Gibt es eine Möglichkeit, über einen Bereich von Ganzzahlen zu iterieren?

174

Der Bereich von Go kann über Karten und Slices iterieren, aber ich habe mich gefragt, ob es eine Möglichkeit gibt, über einen Bereich von Zahlen zu iterieren, etwa so:

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

Oder gibt es eine Möglichkeit, den Bereich von Ganzzahlen in Go so darzustellen, wie Ruby es mit der Klasse Range macht ?

Vishnu
quelle

Antworten:

224

Sie können und sollten einfach eine for-Schleife schreiben. Einfacher, offensichtlicher Code ist der Go-Weg.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
Paul Hankin
quelle
266
Ich glaube nicht, dass die meisten Leute diese Version mit drei Ausdrücken einfacher nennen würden als das, was @Vishnu geschrieben hat. Nur vielleicht nach Jahren der C- oder Java-Indoktrination ;-)
Thomas Ahle
12
IMO ist der Punkt, dass Sie immer diese Version der for-Schleife mit drei Ausdrücken haben werden (dh Sie können viel mehr damit machen, die Syntax aus dem OP ist nur für diesen eingeschränkteren Fall eines Zahlenbereichs gut, also in jeder Sprache, in der Sie diese erweiterte Version wünschen) und sie erfüllt die gleiche Aufgabe ausreichend und unterscheidet sich ohnehin nicht wesentlich. Warum sollten Sie also eine andere Syntax lernen / sich daran erinnern müssen? Wenn Sie in einem großen und komplexen Projekt programmieren, müssen Sie sich bereits Sorgen machen, ohne den Compiler um verschiedene Syntaxen für etwas so Einfaches wie eine Schleife kämpfen zu müssen.
Brad Peabody
3
@ ThomasAhle, besonders wenn man bedenkt, dass C ++ offiziell die Notation for_each (x, y) hinzufügt, die von der Boost-Vorlagenbibliothek inspiriert ist
don bright
5
@BradPeabody das ist eigentlich eine Frage der Präferenz. Python hat keine 3-Ausdrucksschleife und funktioniert einwandfrei. Viele halten die Syntax für jede Syntax für viel weniger fehleranfällig, und nichts ist an sich ineffizient.
VinGarcia
3
@necromancer hier ist ein Beitrag von Rob Pike, der für fast dasselbe argumentiert wie meine Antwort. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Es mag sein, dass die Go-Community anderer Meinung ist, aber wenn sie mit einem der Autoren der Sprache übereinstimmt, kann es keine wirklich schlechte Antwort sein.
Paul Hankin
43

Hier ist ein Programm zum Vergleich der beiden bisher vorgeschlagenen Methoden

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

Kompilieren Sie wie folgt, um eine Demontage zu generieren

go build -gcflags -S iter.go

Hier ist klar (ich habe die Nicht-Anweisungen aus der Liste entfernt)

Konfiguration

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

Schleife

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     ,

Und hier ist with_iter

Konfiguration

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

Schleife

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     ,

Sie sehen also, dass die Iter-Lösung erheblich teurer ist, obwohl sie in der Einrichtungsphase vollständig integriert ist. In der Schleifenphase gibt es eine zusätzliche Anweisung in der Schleife, aber es ist nicht so schlimm.

Ich würde die einfache for-Schleife verwenden.

Nick Craig-Wood
quelle
8
Ich kann nicht "sehen, dass die Iter-Lösung erheblich teurer ist". Ihre Methode zum Zählen von Go-Pseudo-Assembler-Anweisungen ist fehlerhaft. Führen Sie einen Benchmark durch.
PeterSO
11
Eine Lösung ruft an runtime.makesliceund die andere nicht - ich brauche keinen Benchmark, um zu wissen, dass das viel langsamer sein wird!
Nick Craig-Wood
6
Ja runtime.makesliceist klug genug, um keinen Speicher zuzuweisen, wenn Sie nach einer Größenzuweisung von Null fragen. Das Obige nennt es jedoch immer noch und laut Ihrem Benchmark dauert es auf meinem Computer 10 ns länger.
Nick Craig-Wood
4
Dies erinnert an Leute, die aus Leistungsgründen vorschlagen, C über C ++ zu verwenden
Nekromant
5
Die in Goland übliche Laufzeitleistung von Nanosekunden-CPU-Operationen zu diskutieren, erscheint mir albern. Ich würde das nach der Lesbarkeit als eine sehr entfernte letzte Überlegung betrachten. Selbst wenn die CPU-Leistung relevant wäre, überschwemmt der Inhalt der for-Schleife fast immer alle Unterschiede, die durch die Schleife selbst entstehen.
Jonathan Hartley
34

Es wurde von Mark Mishyn vorgeschlagen, Slice zu verwenden, aber es gibt keinen Grund, ein Array mit makeund ein forzurückgegebenes Slice davon zu erstellen , wenn ein über Literal erstelltes Array verwendet werden kann und es kürzer ist

for i := range [5]int{} {
        fmt.Println(i)
}
Daniil Grankin
quelle
8
Wenn Sie die Variable nicht verwenden for range [5]int{} {
möchten,
6
Nachteil ist, dass 5hier ein Literal ist und zur Laufzeit nicht ermittelt werden kann.
Steve Powell
Ist es schneller oder vergleichbar mit normalen drei Ausdrücken für Schleife?
Amit Tripathi
@AmitTripathi Ja, es ist vergleichbar, die Ausführungszeit ist für Milliarden von Iterationen fast gleich.
Daniil Grankin
18

iter ist ein sehr kleines Paket, das nur eine syntantisch andere Möglichkeit bietet, über ganze Zahlen zu iterieren.

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

Rob Pike (ein Autor von Go) hat es kritisiert :

Es scheint, dass fast jedes Mal, wenn jemand einen Weg findet, um zu vermeiden, dass so etwas wie eine for-Schleife auf idiomatische Weise ausgeführt wird, weil es sich zu lang oder umständlich anfühlt, das Ergebnis fast immer mehr Tastenanschläge sind als das, was angeblich kürzer ist. [...] Abgesehen von all dem verrückten Aufwand, den diese "Verbesserungen" mit sich bringen.

Elithrar
quelle
16
Pikes Kritik ist insofern simpel, als sie sich nur mit den Tastenanschlägen befasst und nicht mit dem mentalen Aufwand, ständig Bereiche neu zu deklarieren. Auch mit dem meisten modernen Editoren, die iterverwendeten Version tatsächlich wenige Tastenanschläge da rangeund iterautomatisch vervollständigt werden.
Chris Redford
1
@ lang2, forLoops sind keine erstklassigen Unix-Bürger, wie sie unterwegs sind. Außerdem, im Gegensatz zu for, seqströmt in der Standardausgabe eine Folge von Zahlen. Ob sie darüber iterieren oder nicht, liegt beim Verbraucher. Obwohl dies for i in $(seq 1 10); do ... done in Shell üblich ist, ist es nur eine Möglichkeit, eine for-Schleife zu erstellen, die selbst nur eine Möglichkeit ist, die Ausgabe von zu verbrauchen seq, wenn auch eine sehr häufige.
Daniel Farrell
2
Auch hält Pike einfach nicht die Tatsache , dass eine Kompilierung (angesichts der Sprachdaten eine Reihe Syntax für diesen Anwendungsfall enthalten) bauen in einer Weise, nur behandeln könnte i in range(10)genau wie i := 0; i < 10; i++.
Rouven B.
8

Hier ist ein Benchmark zum Vergleichen einer Go- forAnweisung mit einer ForClause- und einer Go- rangeAnweisung unter Verwendung des iterPakets.

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
}

Ausgabe:

$ 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
quelle
5
Wenn Sie Loops auf 10 setzen und den Benchmark erneut versuchen, sehen Sie einen deutlichen Unterschied. Auf meinem Computer benötigt die ForClause 5,6 ns, während der Iter 15,4 ns benötigt. Das Aufrufen des Allokators (obwohl es klug genug ist, nichts zuzuweisen) kostet also immer noch 10 ns und einen ganzen Haufen zusätzlichen I-Cache-Busting-Codes.
Nick Craig-Wood
Es würde mich interessieren, Ihre Benchmarks und Kritiken für das Paket zu sehen, das ich erstellt und in meiner Antwort referenziert habe .
Chris Redford
5

Obwohl ich Ihre Besorgnis über das Fehlen dieser Sprachfunktion bedauere, möchten Sie wahrscheinlich nur eine normale forSchleife verwenden. Und Sie werden wahrscheinlich mehr damit einverstanden sein, als Sie denken, wenn Sie mehr Go-Code schreiben.

Ich habe dieses Iter-Paket geschrieben, das von einer einfachen, idiomatischen forSchleife unterstützt wird, die Werte über a zurückgibt chan int, um das Design in https://github.com/bradfitz/iter zu verbessern, auf das hingewiesen wurde Caching- und Leistungsprobleme sowie eine clevere, aber seltsame und nicht intuitive Implementierung. Meine eigene Version funktioniert genauso:

package main

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

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

Das Benchmarking ergab jedoch, dass die Verwendung eines Kanals eine sehr teure Option war. Der Vergleich der 3 Methoden, die iter_test.goin meinem Paket mit ausgeführt werden können

go test -bench=. -run=.

quantifiziert, wie schlecht seine Leistung ist

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

Dabei zeigt dieser Benchmark auch, wie bradfitzschlecht die Lösung im Vergleich zur integrierten forKlausel für eine Schleifengröße von ist 10.

Kurz gesagt, es scheint bisher keine Möglichkeit zu geben, die Leistung der integrierten forKlausel zu duplizieren und gleichzeitig eine einfache Syntax für bereitzustellen[0,n) wie in Python und Ruby bereitzustellen.

Das ist eine Schande, denn für das Go-Team wäre es wahrscheinlich einfach, dem Compiler eine einfache Regel hinzuzufügen, um eine Zeile wie zu ändern

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

auf den gleichen Maschinencode wie for i := 0; i < 10; i++.

Um fair zu sein, habe ich nach dem Schreiben meines eigenen iter.NProgramms (aber vor dem Benchmarking) ein kürzlich geschriebenes Programm durchgesehen, um alle Stellen zu sehen, an denen ich es verwenden konnte. Es waren eigentlich nicht viele. Es gab nur eine Stelle in einem nicht wichtigen Abschnitt meines Codes, an der ich ohne die vollständigere Standardklausel auskommen konnte for.

Während es so aussieht, als wäre dies im Prinzip eine große Enttäuschung für die Sprache, werden Sie - wie ich - feststellen, dass Sie es in der Praxis eigentlich nicht wirklich brauchen. Wie Rob Pike für Generika bekannt ist, verpassen Sie diese Funktion möglicherweise nicht so sehr, wie Sie denken.

Chris Redford
quelle
1
Die Verwendung eines Kanals für die Iteration ist sehr teuer. Goroutinen und Kanäle sind billig, sie sind nicht kostenlos. Wenn der iterative Bereich über dem Kanal vorzeitig endet, endet die Goroutine nie (ein Goroutine-Leck). Das Iter Verfahren wurde aus dem gelöschten Vektor Paket . " container / vector: Iter () von der Schnittstelle entfernen (Iter () ist fast nie der richtige Mechanismus zum Aufrufen). " Ihre Iter- Lösung ist immer die teuerste.
PeterSO
4

Wenn Sie nur über einen Bereich ohne Verwendung von und Indizes oder irgendetwas anderem iterieren möchten, hat dieses Codebeispiel für mich gut funktioniert. Keine zusätzliche Erklärung erforderlich, nein _. Ich habe die Leistung jedoch nicht überprüft.

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

PS Der allererste Tag in GoLang. Bitte kritisieren Sie, wenn es ein falscher Ansatz ist.

WHS
quelle
Bisher (Version 1.13.6) funktioniert es nicht. non-constant array boundMich anwerfen .
WHS
1

Sie können auch github.com/wushilin/stream besuchen

Es ist ein fauler Stream wie das Konzept von 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()

Hoffe das hilft

Wu Shilin
quelle
0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}
Dvv Avinash
quelle
1
Fügen Sie Ihrem Code einen Kontext hinzu, damit zukünftige Leser seine Bedeutung besser verstehen können.
Grant Miller
3
Was ist das? Summe ist nicht definiert.
Naftalimich
0

Ich habe in Golang ein Paket geschrieben, das die Bereichsfunktion von Python nachahmt:

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

Hinweis: Ich habe zum Spaß geschrieben! Übrigens kann es manchmal hilfreich sein

Saddam Hossain
quelle