¿Hay alguna forma de iterar en un rango de enteros?

175

El rango de Go puede iterar sobre mapas y sectores, pero me preguntaba si hay una manera de iterar sobre un rango de números, algo como esto:

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

¿O hay una manera de representar el rango de enteros en Go como lo hace Ruby con la clase Range ?

Vishnu
fuente

Respuestas:

225

Puede y debe simplemente escribir un bucle for. El código simple y obvio es Go way.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
Paul Hankin
fuente
269
No creo que la mayoría de la gente llame a esta versión de tres expresiones más simple de lo que escribió @Vishnu. Solo tal vez después de años y años de adoctrinamiento C o Java ;-)
Thomas Ahle
12
En mi opinión, el punto es que siempre tendrá esta versión de tres expresiones del ciclo for (es decir, puede hacer mucho más con él, la sintaxis del OP solo es buena para ese caso más restringido de un rango de números, por lo que en cualquier idioma vas a querer esta versión extendida) y cumple suficientemente la misma tarea, y no es notablemente diferente de todos modos, entonces, ¿por qué tener que aprender / recordar otra sintaxis? Si está codificando en un proyecto grande y complejo, ya tiene suficiente de qué preocuparse sin tener que luchar contra el compilador por varias sintaxis para algo tan simple como un bucle.
Brad Peabody
3
@ThomasAhle, especialmente teniendo en cuenta que C ++ está agregando oficialmente la notación for_each (x, y) inspirada en la biblioteca de plantillas de impulso
don
55
@BradPeabody esto es realmente una cuestión de preferencia. Python no tiene el bucle de 3 expresiones y funciona bien. Muchos consideran que la sintaxis para cada uno es mucho menos propensa a errores y no hay nada intrínsecamente ineficiente al respecto.
VinGarcia
3
@necromancer aquí hay una publicación de Rob Pike argumentando por lo mismo que mi respuesta. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Puede ser que la comunidad Go no esté de acuerdo, pero cuando está de acuerdo con uno de los autores del idioma, no puede ser una respuesta tan mala.
Paul Hankin
43

Aquí hay un programa para comparar las dos formas sugeridas hasta ahora

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 de esta manera para generar el desmontaje

go build -gcflags -S iter.go

Aquí está claro (he eliminado las no instrucciones de la lista)

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á con_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     ,

Entonces puede ver que la solución iter es considerablemente más costosa a pesar de que está totalmente integrada en la fase de configuración. En la fase de bucle hay una instrucción adicional en el bucle, pero no es tan malo.

Yo usaría el simple for loop.

Nick Craig-Wood
fuente
8
No puedo "ver que la solución iter es considerablemente más cara". Su método de contar las instrucciones del pseudoensamblador Go es defectuoso. Ejecute un punto de referencia.
peterSO
11
Una solución llama runtime.makeslicey la otra no. ¡No necesito un punto de referencia para saber que va a ser mucho más lento!
Nick Craig-Wood
66
runtime.makeslicees lo suficientemente inteligente como para no asignar memoria si solicita una asignación de tamaño cero. Sin embargo, lo anterior todavía lo llama, y ​​de acuerdo con su punto de referencia, toma 10nS más en mi máquina.
Nick Craig-Wood
44
esto recuerda a las personas que sugieren usar C sobre C ++ por razones de rendimiento
nigromante
55
Debatir el rendimiento en tiempo de ejecución de las operaciones de CPU en nanosegundos, aunque es común en Goland, me parece una tontería. Lo consideraría una última consideración muy distante, después de la legibilidad. Incluso si el rendimiento de la CPU fuera relevante, el contenido del bucle for casi siempre empantanará las diferencias incurridas por el bucle.
Jonathan Hartley
34

Mark Mishyn le sugirió que usara el segmento, pero no hay ninguna razón para crear una matriz makey usarla en la forsección devuelta cuando la matriz creada a través del literal se puede usar y es más corta

for i := range [5]int{} {
        fmt.Println(i)
}
Daniil Grankin
fuente
8
Si no va a usar la variable, también puede omitir el lado izquierdo y usarfor range [5]int{} {
blockloop
66
El inconveniente es que 5aquí hay un literal y no se puede determinar en tiempo de ejecución.
Steve Powell
¿Es más rápido o comparable a las tres expresiones normales para loop?
Amit Tripathi
@AmitTripathi sí, es comparable, el tiempo de ejecución es casi el mismo para miles de millones de iteraciones.
Daniil Grankin
18

iter es un paquete muy pequeño que solo proporciona una forma sintácticamente diferente de iterar sobre enteros.

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

Rob Pike (autor de Go) lo ha criticado :

Parece que casi cada vez que a alguien se le ocurre una forma de evitar hacer algo así como un bucle for de forma idiomática, porque se siente demasiado largo o engorroso, el resultado es casi siempre más pulsaciones de teclas que lo que supuestamente es más corto. [...] Eso está dejando de lado toda la sobrecarga loca que traen estas "mejoras".

elithrar
fuente
16
La crítica de Pike es simplista en el sentido de que solo aborda las pulsaciones del teclado en lugar de la sobrecarga mental de los rangos de redeterminación constante. Además, con la mayoría de los editores modernos, la iterversión realmente utiliza menos pulsaciones debido rangey iterautocompletará.
Chris Redford
1
@ lang2, los forbucles no son ciudadanos de primera clase de Unix como están en marcha. Además, a diferencia de for, seqarroyos en la salida estándar una secuencia de números. Si se debe iterar sobre ellos o no, depende del consumidor. Aunque for i in $(seq 1 10); do ... done es común en Shell, es solo una forma de hacer un bucle for, que en sí mismo es solo una forma de consumir la salida seq, aunque es muy común.
Daniel Farrell el
2
Además, Pike simplemente no considera el hecho de que una compilación (dado que las especificaciones del lenguaje incluían una sintaxis de rango para este caso de uso) podría construirse de manera tal que se tratara i in range(10)exactamente igual i := 0; i < 10; i++.
Rouven B.
8

Aquí hay un punto de referencia para comparar una fordeclaración Go con una ForClause y una rangedeclaración Go usando el iterpaquete.

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
$
PeterSO
fuente
55
Si establece bucles en 10, vuelva a intentar el punto de referencia y verá una marcada diferencia. En mi máquina, el ForClause toma 5.6 ns mientras que el Iter toma 15.4 ns, por lo que llamar al asignador (aunque es lo suficientemente inteligente como para no asignar nada) todavía cuesta 10ns y un montón de código extra de I-cache.
Nick Craig-Wood
Me interesaría ver sus puntos de referencia y críticas para el paquete que creé y al que hice referencia en mi respuesta .
Chris Redford
5

Si bien me compadezco de su preocupación por la falta de esta función de lenguaje, probablemente solo quiera usar un forbucle normal . Y probablemente estará más de acuerdo con eso de lo que piensa al escribir más código Go.

Escribí este paquete iter , que está respaldado por un forbucle simple e idiomático que devuelve valores sobre un chan int, en un intento de mejorar el diseño que se encuentra en https://github.com/bradfitz/iter , que se ha señalado que tiene problemas de caché y rendimiento, así como una implementación inteligente, pero extraña y poco 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 pueden ejecutar desde iter_test.gomi paquete usando

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 bradfitzsolución tiene un rendimiento inferior en comparación con la forcláusula incorporada para un tamaño de bucle de 10.

En resumen, parece que hasta ahora no se ha descubierto una forma de duplicar el rendimiento de la forcláusula incorporada al tiempo que proporciona una sintaxis simple [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 de 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++.

Sin embargo, para ser justos, después de escribir el mío iter.N(pero antes de compararlo), revisé un programa recientemente escrito para ver todos los lugares donde podría usarlo. En realidad no había muchos. Solo había un lugar, en una sección no vital de mi código, donde podía pasar sin la forcláusula predeterminada más completa .

Entonces, aunque parezca que esto es una gran decepción para el lenguaje en principio, puede encontrar, como lo hice yo, que en realidad no lo necesita en la práctica. Como se sabe que Rob Pike dice para los genéricos, es posible que no se pierda esta característica tanto como cree que lo hará.

Chris Redford
fuente
1
Usar un canal para la iteración es muy costoso; las gorutinas y los canales son baratos, no son gratuitos. Si el rango iterativo sobre el canal termina temprano, la rutina nunca termina (una fuga de la rutina). El método Iter se eliminó del paquete de vectores . " contenedor / vector: eliminar Iter () de la interfaz (Iter () casi nunca es el mecanismo correcto para llamar) " . Su solución iter es siempre la más cara.
peterSO
4

Si desea simplemente iterar sobre un rango sin usar e índices o cualquier otra cosa, esta muestra de código funcionó bien para mí. No se necesita declaración adicional, no _. Sin embargo, no he verificado el rendimiento.

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

PD: El primer día en GoLang. Por favor, critique si es un enfoque equivocado.

WHS
fuente
Hasta ahora (versión 1.13.6), no funciona en. Tirar non-constant array boundde mí.
WHS
1

También puedes visitar github.com/wushilin/stream

Es un flujo lento como el concepto de 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()

Espero que esto ayude

Wu Shilin
fuente
0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}
Dvv Avinash
fuente
1
Agregue algo de contexto a su código para ayudar a los futuros lectores a comprender mejor su significado.
Grant Miller
3
¿Que es esto? suma no está definida.
naftalimich
0

He escrito un paquete en Golang que imita la función de rango de Python:

Paquete 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
}

Nota: ¡He escrito por diversión! Por cierto, a veces puede ser útil

Saddam Hossain
fuente