Avatar ·

Как в Go организовать цикл по диапазону целых чисел без использования for i := 0; i < N; i++?

Всем привет! Я новичок в Go, перешел с Python, и меня немного смущает отсутствие привычной конструкции `for i in range(10)`. В питоне это было очень удобно — просто перебрать числа от 0 до 9, не заботясь о счетчике. В Go я вижу только классический цикл for с тремя компонентами: ```go for i := 0; i < 10; i++ { fmt.Println(i) } ``` Это работает, но выглядит громоздко, особенно когда нужно просто выполнить действие N раз. Есть ли более элегантный способ? Например, что-то вроде `for i := range 10`? Я еще заметил, что в Go есть `range` для срезов, мап и каналов: ```go nums := []int{1, 2, 3} for i, v := range nums { fmt.Println(i, v) } ``` Но для простого диапазона чисел это не работает. Подскажите, может быть есть какой-то идиоматичный способ перебрать числа от A до B (например, от 5 до 15) без явного написания инициализации, условия и инкремента? Или в Go это делается только через классический for? Заранее спасибо! <<>>

Краткий ответ

В Go нет встроенной конструкции for i in range(N) как в Python, но есть несколько идиоматичных способов, которые делают код чище. Самый простой и рекомендуемый — использовать классический for, но с пустыми выражениями, если вам нужно только количество итераций.

Способ 1: Классический for с пустым телом (для N итераций)

Если вам нужно просто выполнить действие N раз, не используя индекс, можно опустить переменную цикла:

package main

import "fmt"

func main() {
    n := 5
    for i := 0; i < n; i++ {
        fmt.Println("Iteration", i)
    }
}

Это стандартный и самый производительный способ. Не пытайтесь его избегать — он идиоматичен для Go.

Способ 2: Использование range с каналом (Go 1.22+)

Начиная с Go 1.22, можно использовать for range с целым числом:

package main

import "fmt"

func main() {
    for i := range 5 {
        fmt.Println(i) // 0, 1, 2, 3, 4
    }
}

Это нововведение очень похоже на Python. Важно: range 5 дает числа от 0 до 4. Если вам нужен диапазон [A, B), используйте:

for i := range 10 {
    if i >= 5 {
        fmt.Println(i) // 5, 6, 7, 8, 9
    }
}

Но это не очень эффективно. Лучше использовать классический for.

Способ 3: Функция-генератор (для сложных диапазонов)

Если вам часто нужны нестандартные диапазоны (с шагом, от A до B), можно написать вспомогательную функцию:

package main

import "fmt"

func Range(start, end int) []int {
    if start > end {
        return nil
    }
    result := make([]int, end-start)
    for i := start; i < end; i++ {
        result[i-start] = i
    }
    return result
}

func main() {
    for _, i := range Range(5, 10) {
        fmt.Println(i) // 5, 6, 7, 8, 9
    }
}

Недостаток: создается срез, что может быть неэффективно для больших диапазонов.

Способ 4: Канал для ленивой генерации

Для больших диапазонов лучше использовать канал:

package main

import "fmt"

func RangeChan(start, end int) <-chan int {
    ch := make(chan int)
    go func() {
        for i := start; i < end; i++ {
            ch <- i
        }
        close(ch)
    }()
    return ch
}

func main() {
    for i := range RangeChan(5, 10) {
        fmt.Println(i) // 5, 6, 7, 8, 9
    }
}

Этот вариант ленивый и не выделяет память под весь диапазон, но добавляет накладные расходы на горутину и канал.

Рекомендации

  • Для простых диапазонов (0..N): используйте for i := 0; i < N; i++ или for i := range N (Go 1.22+).
  • Для диапазонов [A..B): используйте for i := A; i < B; i++.
  • Для шага, отличного от 1: for i := A; i < B; i += step.
  • Избегайте создания срезов для больших диапазонов — это неэффективно по памяти.

Помните: в Go простота и производительность важнее синтаксического сахара. Классический for — это не костыль, а идиома языка.

<<>>

Альтернативный подход: Использование пакета lo (дженерики)

Если вы используете Go 1.18+ и хотите писать в функциональном стиле, можно применить библиотеку github.com/samber/lo, которая предоставляет множество утилит, включая lo.Range:

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    // Генерирует срез [0, 1, 2, 3, 4]
    for _, i := range lo.Range(5) {
        fmt.Println(i)
    }

    // Генерирует срез [2, 3, 4, 5, 6] с шагом 1
    for _, i := range lo.RangeFrom(2, 5) {
        fmt.Println(i)
    }

    // Генерирует срез [0, 2, 4] с шагом 2
    for _, i := range lo.RangeWithSteps(0, 5, 2) {
        fmt.Println(i)
    }
}

Этот подход удобен для цепочек операций (map, filter), но помните: lo.Range создает срез в памяти, поэтому для очень больших диапазонов (миллионы элементов) лучше использовать классический for или канал.

Если вы не хотите добавлять внешнюю зависимость, просто используйте классический for — это самый производительный и понятный способ в Go.