C'è un modo per iterare su un intervallo di numeri interi?


175

L'intervallo di Go può iterare su mappe e sezioni, ma mi chiedevo se esiste un modo per iterare su un intervallo di numeri, qualcosa del genere:

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

O c'è un modo per rappresentare l'intervallo di numeri interi in Go come fa Ruby con la classe Range ?

Risposte:


225

Puoi e dovresti semplicemente scrivere un ciclo for. Il codice semplice e ovvio è la strada da percorrere.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}

269
Non penso che la maggior parte delle persone chiamerebbe questa versione a tre espressioni più semplice di quanto ha scritto @Vishnu. Solo forse dopo anni e anni di indottrinamento C o Java ;-)
Thomas Ahle,

12
Il punto IMO è che avrai sempre questa versione a tre espressioni del ciclo for (cioè puoi fare molto di più con esso, la sintassi dell'OP è buona solo per quel caso più limitato di un intervallo di numeri, quindi in qualsiasi lingua desidererai questa versione estesa) e compie sufficientemente lo stesso compito, e non è comunque notevolmente diverso, quindi perché imparare / ricordare un'altra sintassi. Se stai programmando un progetto ampio e complesso, devi già preoccuparti abbastanza senza dover combattere il compilatore su varie sintassi per qualcosa di semplice come un loop.
Brad Peabody,

3
@ThomasAhle soprattutto considerando che C ++ sta aggiungendo ufficialmente la notazione for_each (x, y) ispirata alla libreria dei template boost
don bright

5
@BradPeabody questa è in realtà una questione di preferenza. Python non ha il ciclo di 3 espressioni e funziona bene. Molti considerano la sintassi per ogni molto meno soggetta a errori e non c'è nulla di intrinsecamente inefficiente al riguardo.
VinGarcia,

3
@necromancer: ecco un post di Rob Pike che sostiene la stessa cosa della mia risposta. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Può darsi che la community di Go non sia d'accordo, ma quando è d'accordo con uno degli autori della lingua, non può essere una brutta risposta.
Paul Hankin,

43

Ecco un programma per confrontare i due modi suggeriti finora

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()
}

Compilare in questo modo per generare lo smontaggio

go build -gcflags -S iter.go

Qui è semplice (ho rimosso le non istruzioni dall'elenco)

impostare

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

ciclo continuo

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     ,

Ed ecco with_iter

impostare

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

ciclo continuo

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     ,

Quindi puoi vedere che la soluzione iter è notevolmente più costosa anche se è completamente integrata nella fase di installazione. Nella fase del loop c'è un'istruzione extra nel loop, ma non è poi così male.

Userei il semplice per loop.


8
Non riesco a "vedere che la soluzione iter è considerevolmente più costosa". Il metodo di conteggio delle istruzioni dello pseudo-assemblatore Go è errato. Esegui un benchmark.
peterSO,

11
Una soluzione chiama runtime.makeslicee l'altra no: non ho bisogno di un benchmark per sapere che sarà molto più lento!
Nick Craig-Wood,

6
Sì, runtime.makesliceè abbastanza intelligente da non allocare memoria se si richiede un'allocazione di dimensioni zero. Comunque quanto sopra lo chiama ancora, e secondo il tuo benchmark impiega 10 nS in più sulla mia macchina.
Nick Craig-Wood,

4
questo ricorda alle persone che suggeriscono di usare C su C ++ per motivi di prestazioni
negromante il

5
Discutere sulle prestazioni di runtime delle operazioni con CPU a nanosecondi, mentre comune in Goland, mi sembra sciocco. Considererei un'ultima considerazione molto distante, dopo la leggibilità. Anche se le prestazioni della CPU fossero rilevanti, i contenuti del ciclo for quasi sempre sommergeranno qualsiasi differenza sostenuta dal loop stesso.
Jonathan Hartley,

34

Mark Mishyn ha suggerito di usare lo slice ma non c'è motivo di creare un array con makee utilizzarlo nello forslice restituito quando l'array creato tramite il valore letterale può essere utilizzato ed è più breve

for i := range [5]int{} {
        fmt.Println(i)
}

8
Se non utilizzerai la variabile, puoi anche omettere il lato sinistro e utilizzarefor range [5]int{} {
blockloop

6
Lo svantaggio è che 5qui è letterale e non può essere determinato in fase di esecuzione.
Steve Powell,

È più veloce o paragonabile alle normali tre espressioni per loop?
Amit Tripathi,

@AmitTripathi sì, è paragonabile, il tempo di esecuzione è quasi lo stesso per miliardi di iterazioni.
Daniil Grankin,

18

iter è un pacchetto molto piccolo che fornisce solo un modo sinteticamente diverso per iterare su interi.

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

Rob Pike (un autore di Go) lo ha criticato :

Sembra che quasi ogni volta che qualcuno trovi un modo per evitare di fare qualcosa come un ciclo for in modo idiomatico, perché sembra troppo lungo o ingombrante, il risultato è quasi sempre più battiture dei tasti che la cosa è presumibilmente più breve. [...] Questo lascia da parte tutto il folle sovraccarico che questi "miglioramenti" portano.


16
La critica di Pike è semplicistica in quanto affronta solo le sequenze di tasti piuttosto che il sovraccarico mentale di intervalli di redeclaring costantemente. Inoltre, con la maggior parte degli editor moderni, la iterversione utilizza in realtà un minor numero di tasti perché rangee iterverrà completata automaticamente.
Chris Redford,

1
@ lang2, i forloop non sono cittadini di prima classe di Unix come se fossero in gioco. Inoltre, diversamente da for, i seqflussi allo standard output generano una sequenza di numeri. L'iterazione su di essi dipende dal consumatore. Sebbene for i in $(seq 1 10); do ... done sia comune in Shell, è solo un modo per fare un ciclo for, che è esso stesso un modo per consumare l'output seq, sebbene molto comune.
Daniel Farrell,

2
Inoltre, Pike semplicemente non considera il fatto che una compilazione (date le specifiche del linguaggio includeva una sintassi di intervallo per questo caso d'uso) potrebbe essere costruita in modo da trattare i in range(10)esattamente come i := 0; i < 10; i++.
Rouven B.

8

Ecco un benchmark per confrontare forun'istruzione Go con una ForClause e rangeun'istruzione Go utilizzando il iterpacchetto.

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
}

Produzione:

$ 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
$

5
Se imposti i loop su 10, quindi riprova il benchmark vedrai una marcata differenza. Sul mio computer ForClause impiega 5,6 ns mentre l'Iter richiede 15,4 ns, quindi chiamare l'allocatore (anche se è abbastanza intelligente da non allocare nulla) costa ancora 10ns e un intero mucchio di codice di busting I-cache extra.
Nick Craig-Wood,

Sarei interessato a vedere i tuoi parametri di riferimento e le critiche per il pacchetto che ho creato e referenziato nella mia risposta .
Chris Redford,

5

Mentre sono d'accordo con la tua preoccupazione per la mancanza di questa funzione linguistica, probabilmente vorrai semplicemente usare un forciclo normale . E probabilmente starai più bene di quello che pensi mentre scrivi più codice Go.

Ho scritto questo pacchetto iter - che è supportato da un semplice forciclo idiomatico che restituisce valori su un chan int- nel tentativo di migliorare il design trovato in https://github.com/bradfitz/iter , che è stato sottolineato per avere problemi di cache e prestazioni, nonché un'implementazione intelligente, ma strana e non intuitiva. La mia versione funziona allo stesso modo:

package main

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

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

Tuttavia, il benchmarking ha rivelato che l'uso di un canale era un'opzione molto costosa. Il confronto dei 3 metodi, che possono essere eseguiti dal iter_test.gomio pacchetto usando

go test -bench=. -run=.

quantifica quanto siano scarse le sue prestazioni

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

Nel processo, questo benchmark mostra anche come la bradfitzsoluzione sottoperforma rispetto alla forclausola integrata per una dimensione del loop di 10.

In breve, sembra che finora non sia stato scoperto alcun modo per duplicare le prestazioni della forclausola incorporata fornendo una sintassi semplice [0,n)come quella trovata in Python e Ruby.

È un peccato perché probabilmente sarebbe facile per il team Go aggiungere una semplice regola al compilatore per cambiare una riga come

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

allo stesso codice macchina di for i := 0; i < 10; i++.

Comunque, per essere onesti, dopo aver scritto il mio iter.N(ma prima di fare un benchmark), sono tornato indietro attraverso un programma scritto di recente per vedere tutti i posti in cui potevo usarlo. In realtà non c'erano molti. C'era solo un punto, in una sezione non vitale del mio codice, in cui potevo cavarmela senza la forclausola predefinita più completa .

Quindi, sebbene possa sembrare che questa sia una grande delusione per la lingua in linea di principio, potresti scoprire - come ho fatto io - che in realtà non ne hai davvero bisogno. Come Rob Pike è noto per i generici, potresti non perdere questa funzionalità tanto quanto pensi di poter fare.


1
L'uso di un canale per l'iterazione è molto costoso; goroutine e canali sono economici, non sono gratuiti. Se l'intervallo iterativo sul canale termina in anticipo, la goroutine non finisce mai (una perdita goroutine). Il metodo Iter è stato eliminato dal pacchetto vettoriale . " container / vector: remove Iter () dall'interfaccia (Iter () non è quasi mai il meccanismo giusto da chiamare). " La tua soluzione iter è sempre la più costosa.
peterSO,

4

Se vuoi solo iterare su un intervallo senza usare e gli indici o qualsiasi altra cosa, questo esempio di codice ha funzionato bene per me. Nessuna dichiarazione aggiuntiva necessaria, no _. Non ho controllato le prestazioni, però.

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

PS Il primo giorno a GoLang. Per favore, fai una critica se è un approccio sbagliato.


Finora (versione 1.13.6), non funziona su. non-constant array boundMi sta lanciando .
WHS

1

Puoi anche consultare github.com/wushilin/stream

È un flusso pigro come il concetto di 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()

Spero che questo ti aiuti


0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}

1
Aggiungi un po 'di contesto al tuo codice per aiutare i futuri lettori a comprenderne meglio il significato.
Grant Miller,

3
cos'è questo? la somma non è definita.
naftalimich,

0

Ho scritto un pacchetto in Golang che imita la funzione di intervallo di Python:

Pacchetto 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: ho scritto per divertimento! A proposito, a volte può essere utile

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.