Есть ли способ перебрать диапазон целых чисел?

175

Диапазон Go может перебирать карты и фрагменты, но мне было интересно, есть ли способ перебора диапазона чисел, что-то вроде этого:

for i := range [1..10] {
    fmt.Println(i)
}

Или есть способ представить диапазон целых чисел в Go, как это делает Ruby с классом Range ?

Вишну
источник

Ответы:

225

Вы можете и должны просто написать цикл for. Простой, очевидный код - это путь Go.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
Пол Ханкин
источник
269
Я не думаю, что большинство людей назвали бы эту версию с тремя выражениями более простой, чем написанная @Vishnu. Только, возможно, после многих лет C или Java идеологической обработки ;-)
Thomas Ahle
12
IMO суть в том, что вы всегда будете иметь эту версию цикла for с тремя выражениями (то есть вы можете сделать с ней гораздо больше, синтаксис из OP хорош только для этого более ограниченного случая диапазона чисел, поэтому на любом языке вы захотите эту расширенную версию), и она в достаточной степени выполняет ту же задачу, и, в любом случае, ничем не отличается, поэтому зачем изучать / запоминать другой синтаксис. Если вы пишете код для большого и сложного проекта, вам уже достаточно о чем беспокоиться, без необходимости бороться с компилятором за различные синтаксисы для чего-то такого простого, как цикл.
Брэд Пибоди
3
@ThomasAhle, особенно с учетом того, что C ++ официально добавляет нотацию for_each (x, y), вдохновленную библиотекой шаблонов наддува
наденьте светлое
5
@BradPeabody это на самом деле вопрос предпочтений. Python не имеет цикла с 3 выражениями и работает нормально. Многие считают, что синтаксис для каждого менее подвержен ошибкам, и в этом нет ничего по сути неэффективного.
VinGarcia
3
@necromancer, вот пост от Роба Пайка, в котором говорится о том же, что и мой ответ. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Возможно, что сообщество Go не соглашается, но когда оно соглашается с одним из авторов языка, это не может быть настолько плохим ответом.
Пол Ханкин
43

Вот программа для сравнения двух предложенных способов

import (
    "fmt"

    "github.com/bradfitz/iter"
)

func p(i int) {
    fmt.Println(i)
}

func plain() {
    for i := 0; i < 10; i++ {
        p(i)
    }
}

func with_iter() {
    for i := range iter.N(10) {
        p(i)
    }
}

func main() {
    plain()
    with_iter()
}

Скомпилируйте как это, чтобы произвести разборку

go build -gcflags -S iter.go

Здесь ясно (я удалил не инструкции из списка)

настроить

0035 (/home/ncw/Go/iter.go:14) MOVQ    $0,AX
0036 (/home/ncw/Go/iter.go:14) JMP     ,38

петля

0037 (/home/ncw/Go/iter.go:14) INCQ    ,AX
0038 (/home/ncw/Go/iter.go:14) CMPQ    AX,$10
0039 (/home/ncw/Go/iter.go:14) JGE     $0,45
0040 (/home/ncw/Go/iter.go:15) MOVQ    AX,i+-8(SP)
0041 (/home/ncw/Go/iter.go:15) MOVQ    AX,(SP)
0042 (/home/ncw/Go/iter.go:15) CALL    ,p+0(SB)
0043 (/home/ncw/Go/iter.go:15) MOVQ    i+-8(SP),AX
0044 (/home/ncw/Go/iter.go:14) JMP     ,37
0045 (/home/ncw/Go/iter.go:17) RET     ,

А вот с_iter

настроить

0052 (/home/ncw/Go/iter.go:20) MOVQ    $10,AX
0053 (/home/ncw/Go/iter.go:20) MOVQ    $0,~r0+-24(SP)
0054 (/home/ncw/Go/iter.go:20) MOVQ    $0,~r0+-16(SP)
0055 (/home/ncw/Go/iter.go:20) MOVQ    $0,~r0+-8(SP)
0056 (/home/ncw/Go/iter.go:20) MOVQ    $type.[]struct {}+0(SB),(SP)
0057 (/home/ncw/Go/iter.go:20) MOVQ    AX,8(SP)
0058 (/home/ncw/Go/iter.go:20) MOVQ    AX,16(SP)
0059 (/home/ncw/Go/iter.go:20) PCDATA  $0,$48
0060 (/home/ncw/Go/iter.go:20) CALL    ,runtime.makeslice+0(SB)
0061 (/home/ncw/Go/iter.go:20) PCDATA  $0,$-1
0062 (/home/ncw/Go/iter.go:20) MOVQ    24(SP),DX
0063 (/home/ncw/Go/iter.go:20) MOVQ    32(SP),CX
0064 (/home/ncw/Go/iter.go:20) MOVQ    40(SP),AX
0065 (/home/ncw/Go/iter.go:20) MOVQ    DX,~r0+-24(SP)
0066 (/home/ncw/Go/iter.go:20) MOVQ    CX,~r0+-16(SP)
0067 (/home/ncw/Go/iter.go:20) MOVQ    AX,~r0+-8(SP)
0068 (/home/ncw/Go/iter.go:20) MOVQ    $0,AX
0069 (/home/ncw/Go/iter.go:20) LEAQ    ~r0+-24(SP),BX
0070 (/home/ncw/Go/iter.go:20) MOVQ    8(BX),BP
0071 (/home/ncw/Go/iter.go:20) MOVQ    BP,autotmp_0006+-32(SP)
0072 (/home/ncw/Go/iter.go:20) JMP     ,74

петля

0073 (/home/ncw/Go/iter.go:20) INCQ    ,AX
0074 (/home/ncw/Go/iter.go:20) MOVQ    autotmp_0006+-32(SP),BP
0075 (/home/ncw/Go/iter.go:20) CMPQ    AX,BP
0076 (/home/ncw/Go/iter.go:20) JGE     $0,82
0077 (/home/ncw/Go/iter.go:20) MOVQ    AX,autotmp_0005+-40(SP)
0078 (/home/ncw/Go/iter.go:21) MOVQ    AX,(SP)
0079 (/home/ncw/Go/iter.go:21) CALL    ,p+0(SB)
0080 (/home/ncw/Go/iter.go:21) MOVQ    autotmp_0005+-40(SP),AX
0081 (/home/ncw/Go/iter.go:20) JMP     ,73
0082 (/home/ncw/Go/iter.go:23) RET     ,

Таким образом, вы можете видеть, что решение iter значительно дороже, хотя оно полностью встроено на этапе установки. В фазе цикла есть дополнительная инструкция в цикле, но это не так уж плохо.

Я бы использовал простой цикл for.

Ник Крейг-Вуд
источник
8
Я не могу «увидеть, что это решение намного дороже». Ваш метод подсчета инструкций Go псевдо-ассемблера неверен. Запустите тест.
peterSO
11
Одно решение вызывает, runtime.makesliceа другое нет - мне не нужен тест, чтобы знать, что он будет намного медленнее!
Ник Крейг-Вуд,
6
Да runtime.makesliceдостаточно умно, чтобы не выделять память, если вы просите о выделении нулевого размера. Однако вышеупомянутое все еще называет это, и согласно твоему тесту действительно занимает 10 нс дольше на моей машине.
Ник Крейг-Вуд,
4
это напоминает людей, предлагающих использовать C над C ++ по соображениям производительности
некромант
5
Обсуждать производительность во время выполнения наносекундных операций с ЦП, хотя это часто встречается в Голанде, мне кажется глупым. Я бы посчитал это очень отдаленным последним соображением после читабельности. Даже если производительность процессора была релевантной, содержимое цикла for почти всегда будет перекрывать любые различия, возникающие в самом цикле.
Джонатан Хартли
34

Марк Мишин предложил использовать слайс, но нет смысла создавать массив makeи использовать его в forвозвращаемом слайсе, когда массив, созданный с помощью литерала, может быть короче

for i := range [5]int{} {
        fmt.Println(i)
}
Даниил Гранкин
источник
8
Если вы не собираетесь использовать переменную, вы также можете опустить левую сторону и использоватьfor range [5]int{} {
blockloop
6
Недостатком является то, что 5здесь это литерал и не может быть определен во время выполнения.
Стив Пауэлл
Это быстрее или сравнимо с обычными тремя выражениями для цикла?
Амит Трипати
@AmitTripathi да, это сопоставимо, время выполнения почти одинаково для миллиардов итераций.
Даниил Гранкин
18

iter - очень маленький пакет, который просто обеспечивает синтетически иной способ перебора целых чисел.

for i := range iter.N(4) {
    fmt.Println(i)
}

Роб Пайк (автор Go) раскритиковал это :

Кажется, что почти каждый раз, когда кто-то придумывает способ избежать выполнения чего-то вроде цикла for идиоматическим способом, потому что он кажется слишком длинным или громоздким, в результате почти всегда нажимается больше клавиш, чем то, что предположительно короче. [...] Это оставляет в стороне все сумасшедшие накладные расходы, которые приносят эти "улучшения".

elithrar
источник
16
Критика Пайка является упрощенной в том смысле, что она касается только нажатий клавиш, а не ментальных накладных расходов на постоянно изменяемые диапазоны. Кроме того , большинство современных редакторов, то iterверсия на самом деле использует меньше нажатий клавиш , потому что rangeи iterбудет автозаполнения.
Крис Редфорд
1
@ lang2, forциклы не являются первоклассным гражданином Unix, как они в ходу. Кроме того, в отличие for, seqпотоки в стандартный вывод последовательность чисел. Стоит ли перебирать их, зависит от потребителя. Хотя for i in $(seq 1 10); do ... done это обычное явление в Shell, это единственный способ сделать цикл for, который сам по себе является единственным способом использования выходных данных seq, хотя и очень распространенным.
Даниэль Фаррелл
2
Кроме того, Пайк просто не учитывает тот факт, что компиляция (с учетом языковых спецификаций, включающая синтаксис диапазона для этого варианта использования) может быть построена таким образом, чтобы он воспринимался i in range(10)точно так же, как и раньше i := 0; i < 10; i++.
Рувен Б.
8

Вот тест для сравнения оператора Go forс ForClause и оператора Go rangeс использованием iterпакета.

iter_test.go

package main

import (
    "testing"

    "github.com/bradfitz/iter"
)

const loops = 1e6

func BenchmarkForClause(b *testing.B) {
    b.ReportAllocs()
    j := 0
    for i := 0; i < b.N; i++ {
        for j = 0; j < loops; j++ {
            j = j
        }
    }
    _ = j
}

func BenchmarkRangeIter(b *testing.B) {
    b.ReportAllocs()
    j := 0
    for i := 0; i < b.N; i++ {
        for j = range iter.N(loops) {
            j = j
        }
    }
    _ = j
}

// It does not cause any allocations.
func N(n int) []struct{} {
    return make([]struct{}, n)
}

func BenchmarkIterAllocs(b *testing.B) {
    b.ReportAllocs()
    var n []struct{}
    for i := 0; i < b.N; i++ {
        n = iter.N(loops)
    }
    _ = n
}

Вывод:

$ go test -bench=. -run=.
testing: warning: no tests to run
PASS
BenchmarkForClause      2000       1260356 ns/op           0 B/op          0 allocs/op
BenchmarkRangeIter      2000       1257312 ns/op           0 B/op          0 allocs/op
BenchmarkIterAllocs 20000000            82.2 ns/op         0 B/op          0 allocs/op
ok      so/test 7.026s
$
peterSO
источник
5
Если вы установите циклы на 10, затем повторите тест, вы увидите заметную разницу. На моей машине ForClause занимает 5,6 нс, в то время как Iter занимает 15,4 нс, поэтому вызов распределителя (даже если он достаточно умен, чтобы ничего не распределять) все еще стоит 10 нс и целую кучу дополнительного кода перебора I-кэша.
Ник Крейг-Вуд,
Мне было бы интересно увидеть ваши тесты и критерии для пакета, который я создал и на который ссылаюсь в моем ответе .
Крис Редфорд
5

Хотя я сожалею о вашей озабоченности отсутствием этой языковой функции, вы, вероятно, просто захотите использовать обычный forцикл. И вам, вероятно, будет лучше с этим, чем вы думаете, когда будете писать больше кода на Go.

Я написал этот пакет iter - который поддерживается простым идиоматическим forциклом, который возвращает значения через a chan int- в попытке улучшить дизайн, найденный на https://github.com/bradfitz/iter , который, как было указано, имеет проблемы с кэшированием и производительностью, а также умная, но странная и не интуитивная реализация. Моя собственная версия работает так же:

package main

import (
    "fmt"
    "github.com/drgrib/iter"
)

func main() {
    for i := range iter.N(10) {
        fmt.Println(i)
    }
}

Однако сравнительный анализ показал, что использование канала было очень дорогим вариантом. Сравнение 3 методов, которые можно запустить iter_test.goв моем пакете, используя

go test -bench=. -run=.

количественно показывает, насколько плохо его производительность

BenchmarkForMany-4                   5000       329956 ns/op           0 B/op          0 allocs/op
BenchmarkDrgribIterMany-4               5    229904527 ns/op         195 B/op          1 allocs/op
BenchmarkBradfitzIterMany-4          5000       337952 ns/op           0 B/op          0 allocs/op

BenchmarkFor10-4                500000000         3.27 ns/op           0 B/op          0 allocs/op
BenchmarkDrgribIter10-4            500000      2907 ns/op             96 B/op          1 allocs/op
BenchmarkBradfitzIter10-4       100000000        12.1 ns/op            0 B/op          0 allocs/op

В процессе этот тест также показывает, как bradfitzрешение работает хуже по сравнению со встроенным forпредложением для размера цикла 10.

Короче говоря, до сих пор не найдено способа дублировать производительность встроенного forпредложения, в то же время предоставляя простой синтаксис, [0,n)подобный тому, который есть в Python и Ruby.

К сожалению, для команды Go было бы легко добавить в компилятор простое правило для изменения строки, например:

for i := range 10 {
    fmt.Println(i)
}

на тот же машинный код, что и for i := 0; i < 10; i++.

Однако, чтобы быть справедливым, после написания своей собственной iter.N(но до сравнительной оценки) я вернулся к недавно написанной программе, чтобы увидеть все места, где я мог ее использовать. Там на самом деле не было много. В неосновной части моего кода было только одно место, где я мог обойтись без более полного forпредложения по умолчанию .

Таким образом, хотя это может показаться огромным разочарованием для языка в принципе, вы можете обнаружить - как и я - что вам на самом деле это не нужно на практике. Как известно, Роб Пайк говорит о дженериках, вы можете не пропустить эту функцию так сильно, как вы думаете.

Крис Редфорд
источник
1
Использование канала для итерации очень дорого; рутины и каналы дешевы, они не бесплатны. Если итеративный диапазон по каналу заканчивается рано, процедура никогда не завершается (утечка программы). Метод Iter был удален из векторного пакета . " container / vector: удалить Iter () из интерфейса (Iter () почти никогда не является правильным механизмом для вызова). " Ваше решение iter всегда самое дорогое.
peterSO
4

Если вы хотите просто перебрать диапазон без использования индексов или чего-либо еще, этот пример кода работал для меня просто отлично. Никакой дополнительной декларации не требуется, нет _. Хотя не проверял производительность.

for range [N]int{} {
    // Body...
}

PS Самый первый день в GoLang. Пожалуйста, сделайте критику, если это неправильный подход.

WHS
источник
Пока (версия 1.13.6) это не работает. Бросать non-constant array boundна меня.
WHS
1

Вы также можете проверить github.com/wushilin/stream

Это ленивый поток, похожий на концепцию java.util.stream.

// It doesn't really allocate the 10 elements.
stream1 := stream.Range(0, 10)

// Print each element.
stream1.Each(print)

// Add 3 to each element, but it is a lazy add.
// You only add when consume the stream
stream2 := stream1.Map(func(i int) int {
    return i + 3
})

// Well, this consumes the stream => return sum of stream2.
stream2.Reduce(func(i, j int) int {
    return i + j
})

// Create stream with 5 elements
stream3 := stream.Of(1, 2, 3, 4, 5)

// Create stream from array
stream4 := stream.FromArray(arrayInput)

// Filter stream3, keep only elements that is bigger than 2,
// and return the Sum, which is 12
stream3.Filter(func(i int) bool {
    return i > 2
}).Sum()

Надеюсь это поможет

У Шилин
источник
0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}
Двв авинаш
источник
1
Добавьте некоторый контекст в свой код, чтобы помочь будущим читателям лучше понять его значение.
Грант Миллер
3
что это? сумма не определена.
нафталимич
0

Я написал пакет на Голанге, который имитирует функцию диапазона Python:

Пакет https://github.com/thedevsaddam/iter

package main

import (
    "fmt"

    "github.com/thedevsaddam/iter"
)

func main() {
    // sequence: 0-9
    for v := range iter.N(10) {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
    // output: 0 1 2 3 4 5 6 7 8 9

    // sequence: 5-9
    for v := range iter.N(5, 10) {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
    // output: 5 6 7 8 9

    // sequence: 1-9, increment by 2
    for v := range iter.N(5, 10, 2) {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
    // output: 5 7 9

    // sequence: a-e
    for v := range iter.L('a', 'e') {
        fmt.Printf("%s ", string(v))
    }
    fmt.Println()
    // output: a b c d e
}

Примечание: я написал для удовольствия! Кстати, иногда это может быть полезно

Саддам Хоссейн
источник