Existe-t-il un moyen d'itérer sur une plage d'entiers?

175

La plage de Go peut itérer sur des cartes et des tranches, mais je me demandais s'il existe un moyen d'itérer sur une plage de nombres, quelque chose comme ceci:

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

Ou y a-t-il un moyen de représenter une plage d'entiers dans Go comme le fait Ruby avec la classe Range ?

Vishnu
la source

Réponses:

225

Vous pouvez, et devriez, simplement écrire une boucle for. Un code simple et évident est la voie à suivre.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
Paul Hankin
la source
269
Je ne pense pas que la plupart des gens appelleraient cette version à trois expressions plus simple que ce que @Vishnu a écrit. Seulement peut-être après des années et des années d'endoctrinement C ou Java ;-)
Thomas Ahle
12
OMI, le fait est que vous aurez toujours cette version à trois expressions de la boucle for (c'est-à-dire que vous pouvez faire beaucoup plus avec elle, la syntaxe de l'OP n'est bonne que pour ce cas plus restreint d'une plage de nombres, donc dans n'importe quelle langue que vous allez vouloir cette version étendue) et elle accomplit suffisamment la même tâche, et n'est pas remarquablement différente de toute façon, alors pourquoi avoir à apprendre / se souvenir d'une autre syntaxe. Si vous codez sur un projet volumineux et complexe, vous en avez déjà assez de vous inquiéter sans avoir à lutter contre le compilateur à propos de diverses syntaxes pour quelque chose d'aussi simple qu'une boucle.
Brad Peabody
3
@ThomasAhle, en particulier compte tenu du fait que C ++ ajoute officiellement la notation for_each (x, y) inspirée de la bibliothèque de modèles boost
don bright
5
@BradPeabody, c'est en fait une question de préférence. Python n'a pas la boucle à 3 expressions et fonctionne très bien. Beaucoup considèrent la syntaxe for-each comme beaucoup moins sujette aux erreurs et il n'y a rien d'inefficace en soi.
VinGarcia
3
@necromancer voici un post de Rob Pike qui plaide pour à peu près la même chose que ma réponse. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Il se peut que la communauté Go soit en désaccord, mais lorsqu'elle est d'accord avec l'un des auteurs de la langue, la réponse ne peut pas vraiment être si mauvaise.
Paul Hankin
43

Voici un programme pour comparer les deux méthodes suggérées jusqu'à présent

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

Compilez comme ça pour générer le désassemblage

go build -gcflags -S iter.go

Voici clair (j'ai supprimé les instructions non de la liste)

installer

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

boucle

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     ,

Et voici avec_iter

installer

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

boucle

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     ,

Ainsi, vous pouvez voir que la solution iter est considérablement plus chère même si elle est entièrement intégrée dans la phase de configuration. Dans la phase de boucle, il y a une instruction supplémentaire dans la boucle, mais ce n'est pas trop mal.

J'utiliserais la simple boucle for.

Nick Craig-Wood
la source
8
Je ne peux pas "voir que la solution iter est considérablement plus chère". Votre méthode de comptage des instructions du pseudo-assembleur Go est erronée. Exécutez un benchmark.
peterSO
11
Une solution appelle runtime.makesliceet l'autre pas - je n'ai pas besoin d'un benchmark pour savoir que ça va être beaucoup plus lent!
Nick Craig-Wood
6
Yes runtime.makesliceest suffisamment intelligent pour ne pas allouer de mémoire si vous demandez une allocation de taille nulle. Cependant, ce qui précède l'appelle toujours, et selon votre benchmark, cela prend 10 nS de plus sur ma machine.
Nick Craig-Wood
4
cela rappelle les gens suggérant d'utiliser C sur C ++ pour des raisons de performances
nécromancien
5
Débattre des performances d'exécution des opérations CPU en nanosecondes, bien que courant dans Goland, me semble idiot. Je considère que c'est une dernière considération très lointaine, après la lisibilité. Même si les performances du processeur étaient pertinentes, le contenu de la boucle for submergera presque toujours les différences encourues par la boucle elle-même.
Jonathan Hartley
34

Il a été suggéré par Mark Mishyn d'utiliser slice, mais il n'y a aucune raison de créer un tableau avec makeet de l'utiliser dans la fortranche renvoyée de celui-ci lorsqu'un tableau créé via un littéral peut être utilisé et qu'il est plus court

for i := range [5]int{} {
        fmt.Println(i)
}
Daniil Grankin
la source
8
Si vous n'utilisez pas la variable, vous pouvez également omettre le côté gauche et utiliserfor range [5]int{} {
blockloop
6
L'inconvénient est qu'il 5s'agit d'un littéral et ne peut pas être déterminé au moment de l'exécution.
Steve Powell
Est-ce plus rapide ou comparable aux trois expressions normales pour la boucle?
Amit Tripathi
@AmitTripathi oui, c'est comparable, le temps d'exécution est presque le même pour des milliards d'itérations.
Daniil Grankin
18

iter est un tout petit package qui fournit juste une manière syntantiquement différente d'itérer sur des entiers.

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

Rob Pike (un auteur de Go) l' a critiqué :

Il semble que presque chaque fois que quelqu'un trouve un moyen d'éviter de faire quelque chose comme une boucle for de manière idiomatique, car cela semble trop long ou trop encombrant, le résultat est presque toujours plus de frappes que ce qui est censé être plus court. [...] Cela laisse de côté tous les frais généraux fous que ces "améliorations" apportent.

élithrar
la source
16
La critique de Pike est simpliste en ce qu'elle ne concerne que les frappes au clavier plutôt que la surcharge mentale de la redéfinition constante des plages. De plus, avec la plupart des éditeurs modernes, la iterversion utilise en fait moins de frappes, car rangeet iterse complétera automatiquement.
Chris Redford
1
@ lang2, les forboucles ne sont pas des citoyens de premier ordre d'Unix comme elles le sont. En outre, à la différence for, les seqflux de sortie standard d' une séquence de nombres. Il appartient au consommateur de décider ou non de les parcourir. Bien que ce for i in $(seq 1 10); do ... done soit courant dans Shell, ce n'est qu'une façon de faire une boucle for, qui n'est en soi qu'une façon de consommer la sortie de seq, bien que très courante.
Daniel Farrell
2
De plus, Pike ne considère tout simplement pas le fait qu'une compilation (étant donné que les spécifications du langage incluent une syntaxe de plage pour ce cas d'utilisation) pourrait être construite de manière à traiter i in range(10)exactement comme i := 0; i < 10; i++.
Rouven B.
8

Voici un benchmark pour comparer une forinstruction Go avec une ForClause et une rangeinstruction Go à l'aide du iterpackage.

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
}

Production:

$ 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
la source
5
Si vous définissez les boucles sur 10, puis réessayez le benchmark, vous verrez une différence marquée. Sur ma machine, ForClause prend 5,6 ns alors que Iter prend 15,4 ns, donc appeler l'allocateur (même s'il est assez intelligent pour ne rien allouer) coûte toujours 10 ns et tout un tas de code de contournement I-cache supplémentaire.
Nick Craig-Wood
Je serais intéressé de voir vos repères et critiques pour le package que j'ai créé et référencé dans ma réponse .
Chris Redford
5

Bien que je compatisse avec votre inquiétude concernant l'absence de cette fonctionnalité de langue, vous allez probablement vouloir simplement utiliser un for boucle . Et vous serez probablement plus d'accord avec cela que vous ne le pensez en écrivant plus de code Go.

J'ai écrit ce package iter - qui est soutenu par une simple forboucle idiomatique qui renvoie des valeurs sur a chan int- dans le but d'améliorer la conception trouvée dans https://github.com/bradfitz/iter , qui a été signalée comme ayant des problèmes de mise en cache et de performances, ainsi qu'une implémentation intelligente, mais étrange et peu intuitive. Ma propre version fonctionne de la même manière:

package main

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

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

Cependant, l'analyse comparative a révélé que l'utilisation d'un canal était une option très coûteuse. La comparaison des 3 méthodes, qui peuvent être exécutées à partir iter_test.gode mon package en utilisant

go test -bench=. -run=.

quantifie à quel point ses performances sont médiocres

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

Dans le processus, ce test de bradfitzperformance montre également comment la solution sous-performante par rapport à la forclause intégrée pour une taille de boucle de 10.

En bref, il ne semble y avoir aucun moyen découvert jusqu'à présent de dupliquer les performances de la forclause intégrée tout en fournissant une syntaxe simple pour[0,n) comme celle trouvée dans Python et Ruby.

Ce qui est dommage car il serait probablement facile pour l'équipe Go d'ajouter une règle simple au compilateur pour changer une ligne comme

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

au même code machine que for i := 0; i < 10; i++.

Cependant, pour être honnête, après avoir écrit le mien iter.N(mais avant de le comparer), je suis revenu sur un programme récemment écrit pour voir tous les endroits où je pouvais l'utiliser. Il n'y en avait pas beaucoup. Il n'y avait qu'un seul endroit, dans une section non vitale de mon code, où je pourrais me débrouiller sans la forclause par défaut plus complète .

Donc, même si cela peut sembler être une énorme déception pour la langue en principe, vous pouvez constater - comme je l'ai fait - que vous n'en avez pas vraiment besoin dans la pratique. Comme Rob Pike est connu pour le dire pour les génériques, vous ne manquerez peut-être pas cette fonctionnalité autant que vous le pensez.

Chris Redford
la source
1
L'utilisation d'un canal pour l'itération est très coûteuse; les goroutines et les canaux sont bon marché, ils ne sont pas gratuits. Si la plage itérative sur le canal se termine tôt, la goroutine ne se termine jamais (une fuite de goroutine). La méthode Iter a été supprimée du package vectoriel . " conteneur / vecteur: supprimez Iter () de l'interface (Iter () n'est presque jamais le bon mécanisme à appeler). " Votre solution iter est toujours la plus chère.
peterSO
4

Si vous souhaitez simplement itérer sur une plage sans utiliser d'index et ou quoi que ce soit d'autre, cet exemple de code a très bien fonctionné pour moi. Aucune déclaration supplémentaire nécessaire, non _. Je n'ai pas vérifié les performances, cependant.

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

PS Le tout premier jour à GoLang. S'il vous plaît, faites une critique si c'est une mauvaise approche.

WHS
la source
Jusqu'à présent (version 1.13.6), cela ne fonctionne pas. Jeter non-constant array boundsur moi.
WHS
1

Vous pouvez également consulter github.com/wushilin/stream

C'est un flux paresseux comme le concept 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()

J'espère que cela t'aides

Wu Shilin
la source
0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}
Dvv Avinash
la source
1
Ajoutez du contexte à votre code pour aider les futurs lecteurs à mieux comprendre sa signification.
Grant Miller
3
Qu'est-ce que c'est? la somme n'est pas définie.
naftalimich
0

J'ai écrit un package dans Golang qui imite la fonction range de Python:

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

Remarque: j'ai écrit pour le plaisir! Btw, parfois cela peut être utile

Saddam Hossain
la source