¿Hay alguna forma de iterar en un rango de números enteros en Golang?
(7)
El rango de Golang puede iterar sobre mapas y sectores, pero me preguntaba si hay una forma de iterar sobre un rango de números, algo como esto
for i := range [1..10] {
fmt.Println(i)
}
o hay una forma de representar el rango de enteros en Go, como lo hace Ruby?
Aquí hay un programa para comparar las dos formas sugeridas hasta el momento
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()
}
Compila así para generar el desmontaje
go build -gcflags -S iter.go
Aquí está claro (eliminé las instrucciones no del listado)
preparar
0035 (/home/ncw/Go/iter.go:14) MOVQ $0,AX
0036 (/home/ncw/Go/iter.go:14) JMP ,38
lazo
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 ,
Y aquí está with_iter
preparar
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
lazo
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 ,
Por lo tanto, puede ver que la solución iter es considerablemente más costosa a pesar de que está totalmente en línea en la fase de configuración. En la fase de ciclo hay una instrucción adicional en el ciclo, pero no es tan malo.
Yo usaría el simple for loop.
Aquí hay un punto de referencia para comparar una declaración de Go for
con una declaración de range
ForClause y de Go usando el paquete 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
}
Salida:
$ 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
$
Aunque compadezco con tu preocupación acerca de la falta de esta función de idioma, probablemente solo desees utilizar un ciclo for
normal. Y probablemente estarás más de acuerdo con eso de lo que crees a medida que escribes más código Go.
Escribí este paquete iter , que está respaldado por un bucle simple e idiomático que devuelve valores sobre un chan int
, en un intento de mejorar el diseño encontrado en iter , que ha sido apuntado tener problemas de almacenamiento en caché y rendimiento, así como una implementación inteligente, pero extraña e intuitiva. Mi propia versión funciona de la misma manera:
package main
import (
"fmt"
"github.com/drgrib/iter"
)
func main() {
for i := range iter.N(10) {
fmt.Println(i)
}
}
Sin embargo, la evaluación comparativa reveló que el uso de un canal era una opción muy costosa. La comparación de los 3 métodos, que se puede ejecutar desde iter_test.go
en mi paquete utilizando
go test -bench=. -run=.
cuantifica cuán pobre es su rendimiento
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
En el proceso, este punto de referencia también muestra cómo la solución bradfitz
rendimiento inferior en comparación con la cláusula incorporada para un tamaño de bucle de 10
.
En resumen, parece que no hay manera de descubrir hasta el momento para duplicar el rendimiento de la cláusula built for
mientras que proporciona una sintaxis simple para [0,n)
como la que se encuentra en Python y Ruby.
Lo cual es una pena porque probablemente sería fácil para el equipo Go agregar una regla simple al compilador para cambiar una línea como
for i := range 10 {
fmt.Println(i)
}
al mismo código de máquina que for i := 0; i < 10; i++
for i := 0; i < 10; i++
for i := 0; i < 10; i++
.
Sin embargo, para ser justos, después de escribir mi propio iter.N
(pero antes de compararlo), volví a través de un programa recientemente escrito para ver todos los lugares donde podría usarlo. En realidad, no hubo muchos. Solo había un lugar, en una sección no vital de mi código, en el que podía vivir sin la cláusula for
cláusula predeterminada más completa.
Entonces, si bien puede parecer que esto es una gran decepción para el lenguaje en principio, puede encontrar, como yo lo hice, que realmente no lo necesita en la práctica. Al igual que Rob Pike es conocido por sus genéricos, es posible que en realidad no se pierda esta característica tanto como cree que lo hará.
Mark Mishyn sugirió usar slice pero no hay razón para crear una matriz con make
y use in for
porción devuelta de ella cuando la matriz creada a través de literal se puede usar y es más corta.
for i := range [5]int{} {
fmt.Println(i)
}
Puede, y debería, simplemente escribir un ciclo para. El código simple y obvio es el camino correcto.
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
También puedes consultar github.com/wushilin/stream
Es un concepto de flujo diferido como java.util.stream.
import "github.com/wushilin/stream"
stream1 := stream.Range(0, 10) // it doesn''t really allocate the
// 10 elements
stream1.Each(print) // => print each element
stream2 := stream1.Map(func(i int) int) {
return i + 3
}) // Add 3 to each element, but it is a lazy add.
// You only add when consume the stream
stream2.Reduce(func(i, j int) int {
return i + j
}) // well, this consumes the stream => return sum of stream2
stream3 := stream.Of(1, 2,3,4,5) // create stream with 5 elements
stream4 := stream.FromArray(arrayInput) // create stream from array
stream3.Filter(func(i int) bool {
return i > 2
}).Sum() => Filter stream3, keep only elements that is bigger than
//2, and return the Sum, which is 12
Espero que esto ayude
iter es un paquete muy pequeño que simplemente proporciona una forma sintácticamente diferente de iterar sobre enteros.
for i := range iter.N(4) {
fmt.Println(i)
}
Rob Pike (un autor de Go) lo ha criticado :
Parece que casi cada vez que alguien encuentra una manera de evitar hacer algo así como un bucle de forma idiomática, porque se siente demasiado largo o engorroso, el resultado es casi siempre más teclas que lo que supuestamente es más corto. [...] Eso está dejando de lado todos los sobrecostos que estas "mejoras" traen.