Ответ 1
Вы можете и должны просто написать цикл for. Простой, очевидный код - это путь Go.
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
Диапазон Golang может перебирать карты и срезы, но мне было интересно, есть ли способ перебирать диапазон чисел, что-то вроде этого
for i := range [1..10] {
fmt.Println(i)
}
или есть способ представить диапазон целых чисел в Go, как это делает ruby?
Вы можете и должны просто написать цикл for. Простой, очевидный код - это путь Go.
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
Вот программа для сравнения двух способов, предложенных до сих пор
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 ,
И вот с_имя
Настройка
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 значительно дороже, хотя оно полностью включено в фазу установки. В фазе цикла в цикле есть дополнительная инструкция, но это не так уж плохо.
Я бы использовал простой цикл.
Марку Мишину было предложено использовать срез, но нет причин создавать массив с make
и использовать в for
возвращенном его фрагменте, когда массив, созданный через литерал, может использоваться и короче
for i := range [5]int{} {
fmt.Println(i)
}
iter - очень маленький пакет, который просто предоставляет синтаксически отличный способ перебора целых чисел.
for i := range iter.N(4) {
fmt.Println(i)
}
Роб Пайк (автор Go) критиковал его:
Кажется, что почти каждый раз, когда кто-то придумывает способ избежать делая что-то вроде цикла for идиоматическим способом, потому что он чувствует слишком долго или громоздко, результат почти всегда больше нажатий клавиш чем то, что предположительно короче. [...] Что оставляет в стороне все сумасшедшие накладные расходы, эти "улучшения" приносят.
Здесь приведен пример сравнения инструкции 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
$
В то время как я сочувствую вашей обеспокоенности отсутствием этой языковой функции, вы, вероятно, просто захотите использовать обычный цикл for
. И вы, вероятно, будете в порядке с этим, чем вы думаете, когда пишете больше кода Go.
Я написал этот пакет iter - который поддерживается простым, идиоматическим циклом for
, который возвращает значения над 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)
}
}
Однако, сравнительный анализ показал, что использование канала было очень дорогостоящим вариантом. Сравнение трех методов, которые можно запустить из 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
.
Так что, хотя может показаться, что это огромное разочарование для языка в принципе, вы можете найти - как и я, - что на самом деле вам это действительно не нужно. Как известно, Роб Пайк говорит о дженериках, вы, возможно, не пропустите эту функцию так сильно, как вы думаете.
Существует уродливое решение, но оно немного ближе к Ruby for i in 0..N
или Python for i in range(N)
for i := range make([]int, 5){
fmt.Println(i)
}
Итак, да, вы должны использовать цикл for i:=0; i<N; i++ {}
или iter
.
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
for _, num := range nums {
fmt.Println(num, sum)
}
}
Если вы хотите просто перебрать диапазон без использования индексов или чего-либо еще, этот пример кода работал для меня просто отлично. Никакой дополнительной декларации не требуется, нет _
. Тем не менее, не проверял производительность.
for range [N]int{} {
// Body...
}
P.S. Самый первый день в GoLang. Пожалуйста, сделайте критику, если это неправильный подход.
Вы также можете проверить 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()
Надеюсь это поможет