La plage de Golang peut parcourir les cartes et les tranches, mais je me demandais s’il existait un moyen de parcourir 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 Ruby?
Vous pouvez et devriez simplement écrire une boucle for. Le code simple et évident est le moyen d'aller.
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
Voici un programme pour comparer les deux manières suggérées jusqu'ici
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()
}
Compiler comme ceci pour générer le désassemblage
go build -gcflags -S iter.go
Voici la plaine (j'ai supprimé les non instructions de la liste)
installer
0035 (/home/ncw/Go/iter.go:14) MOVQ $0,AX
0036 (/home/ncw/Go/iter.go:14) JMP ,38
loop
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 with_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
loop
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 totalement intégrée dans la phase d’installation. Dans la phase de boucle, il y a une instruction supplémentaire dans la boucle, mais ce n'est pas si mal.
Je voudrais utiliser le simple pour la boucle.
Mark Mishyn a suggéré d'utiliser slice, mais il n'y a aucune raison de créer un tableau avec make
et de l'utiliser dans for
a renvoyé une tranche lorsque ce dernier a été créé et qu'il est plus court
for i := range [5]int{} {
fmt.Println(i)
}
iter est un très petit paquet qui fournit simplement une manière différente de parcourir des nombres 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 faire quelque chose comme une boucle for idiomatique, parce que ça sent trop long ou fastidieux, le résultat est presque toujours plus de frappes que la chose qui est soi-disant plus courte. [...] Cela laisse de côté toutes les dépenses folles que ces "améliorations" apportent.
Voici un point de repère pour comparer une instruction Go for
avec une instruction ForClause et une instruction Go range
à l'aide du package 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
}
Sortie:
$ 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
$
Bien que je comprenne votre inquiétude à propos de l'absence de cette fonctionnalité de langage, vous allez probablement vouloir utiliser une boucle for
normale. Et cela vous conviendra probablement mieux que vous ne le pensez en écrivant plus de code Go.
J'ai écrit this iter package - qui s'appuie sur une simple boucle idiomatique for
qui renvoie les valeurs sur un chan int
- dans le but d'améliorer la conception trouvée dans https://github.com/bradfitz/iter , qui a été signalé comme ayant des problèmes de mise en cache et de performances, ainsi que par une implémentation astucieuse, 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)
}
}
Toutefois, 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 pouvant être exécutées à partir de iter_test.go
dans mon paquet 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
Au cours du processus, ce test de performance montre également comment la solution bradfitz
sous-exécute par rapport à la clause for
intégrée pour une taille de boucle de 10
.
En résumé, il semble jusqu'à présent impossible de reproduire les performances de la clause for
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 modifier une ligne comme celle-ci.
for i := range 10 {
fmt.Println(i)
}
au même code machine que for i := 0; i < 10; i++
.
Cependant, pour être juste, après avoir écrit mon propre iter.N
(mais avant de le comparer), je suis revenu dans un programme récemment écrit pour voir tous les endroits où je pouvais l'utiliser. En réalité, ils n'étaient pas nombreux. Il n'y avait qu'un seul endroit, dans une section non essentielle de mon code, où je pouvais me débrouiller sans la clause for
par défaut plus complète.
Ainsi, même si cela peut paraître décevant en principe pour la formulation, vous constaterez peut-être - comme je l’ai fait auparavant - que vous n’en avez pas vraiment besoin dans la pratique. Comme Rob Pike est connu pour ses génériques, vous ne manquerez peut-être pas cette fonctionnalité autant que vous le pensez.
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
for _, num := range nums {
fmt.Println(num, sum)
}
}
Si vous souhaitez simplement parcourir une plage sans utiliser ni index ni quoi que ce soit d'autre, cet exemple de code a parfaitement fonctionné pour moi. Aucune déclaration supplémentaire nécessaire, pas de _
. Je n'ai pas vérifié la performance, cependant.
for range [N]int{} {
// Body...
}
P.S. Le tout premier jour à GoLang. S'il vous plaît, faites une critique si c'est une mauvaise approche.
Vous pouvez également consulter github.com/wushilin/stream
C'est un flux lazy comme concept de 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
J'espère que cela t'aides