Czy istnieje sposób na iterację w zakresie liczb całkowitych?


174

Zakres Go może iterować po mapach i wycinkach, ale zastanawiałem się, czy istnieje sposób na iterację w zakresie liczb, coś takiego:

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

A może istnieje sposób na przedstawienie zakresu liczb całkowitych w Go, tak jak robi to Ruby z klasą Range ?

Odpowiedzi:


224

Możesz i powinieneś po prostu napisać pętlę for. Prosty, oczywisty kod to droga Go.

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

268
Nie sądzę, by większość ludzi nazwała tę wersję z trzema wyrażeniami prostszą niż to, co napisał @Vishnu. Tylko może po latach i latach indoktrynacji w C lub Javie ;-)
Thomas Ahle

12
IMO chodzi o to, że zawsze będziesz mieć tę trzywyrażeniową wersję pętli for (tj. Możesz z nią zrobić o wiele więcej, składnia z OP jest dobra tylko dla tego bardziej ograniczonego przypadku zakresu liczbowego, więc w jakimkolwiek języku, w którym będziesz potrzebować tej rozszerzonej wersji) i wystarczająco wykonuje to samo zadanie, i tak się nie różni, więc po co uczyć się / zapamiętywać inną składnię. Jeśli piszesz w dużym i złożonym projekcie, już masz wystarczająco dużo zmartwień, bez konieczności walki z kompilatorem o różne składnie w celu znalezienia czegoś tak prostego jak pętla.
Brad Peabody,

3
@ThomasAhle, szczególnie biorąc pod uwagę C ++, oficjalnie dodaje notację for_each (x, y) inspirowaną biblioteką szablonów boost
don bright

5
@BradPeabody to właściwie kwestia preferencji. Python nie ma pętli z trzema wyrażeniami i działa dobrze. Wielu uważa, że ​​składnia for-each jest dużo mniej podatna na błędy i nie ma w niej nic nieefektywnego.
VinGarcia

3
@necromancer tutaj jest postem Roba Pike'a, w którym argumentuje o tym samym, co moja odpowiedź. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Być może społeczność Go nie zgadza się, ale jeśli zgadza się z jednym z autorów języka, odpowiedź nie może być tak zła.
Paul Hankin

43

Oto program do porównania dwóch sugerowanych do tej pory sposobów

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

Skompiluj w ten sposób, aby wygenerować demontaż

go build -gcflags -S iter.go

Oto proste (usunąłem nie instrukcje z aukcji)

Ustawiać

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

pętla

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     ,

A oto with_iter

Ustawiać

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

pętla

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     ,

Możesz więc zobaczyć, że rozwiązanie iterowe jest znacznie droższe, mimo że jest w pełni wbudowane w fazie konfiguracji. W fazie pętli w pętli znajduje się dodatkowa instrukcja, ale nie jest tak źle.

Użyłbym prostej pętli for.


8
Nie widzę, że „iterowe rozwiązanie jest znacznie droższe”. Twoja metoda zliczania instrukcji pseudo-assemblera Go jest wadliwa. Przeprowadź test porównawczy.
peterSO

11
Jedno rozwiązanie wywołuje, runtime.makeslicea drugie nie - nie potrzebuję testu porównawczego, aby wiedzieć, że będzie dużo wolniej!
Nick Craig-Wood

6
Tak runtime.makeslicejest na tyle sprytne, aby nie przydzielać żadnej pamięci, jeśli poprosisz o alokację zerowego rozmiaru. Jednak powyższe nadal to nazywa i według twojego testu porównawczego zajmuje to 10 nS dłużej na moim komputerze.
Nick Craig-Wood

4
to przypomina o ludziach sugerujących używanie C zamiast C ++ ze względów wydajnościowych
nekromanta

5
Debatowanie nad wydajnością nanosekundowych operacji procesora w czasie wykonywania, choć powszechne w Goland, wydaje mi się głupie. Uważam to za bardzo odległą ostatnią uwagę, po czytelności. Nawet jeśli wydajność procesora była istotna, zawartość pętli for prawie zawsze zapycha wszelkie różnice spowodowane przez samą pętlę.
Jonathan Hartley

34

Mark Mishyn zasugerował użycie plasterka, ale nie ma powodu, aby tworzyć tablicę z makei używać w forzwracanym plastrze, gdy można użyć tablicy utworzonej za pomocą literału i jest ona krótsza

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

8
Jeśli nie zamierzasz używać zmiennej, możesz również pominąć lewą stronę i użyćfor range [5]int{} {
blockloop

6
Wadą jest to, że 5jest to literał i nie można go określić w czasie wykonywania.
Steve Powell

Czy jest szybszy lub porównywalny z normalnymi trzema wyrażeniami for loop?
Amit Tripathi

@AmitTripathi tak, jest porównywalny, czas wykonania jest prawie taki sam dla miliardów iteracji.
Daniil Grankin

18

iter to bardzo mały pakiet, który po prostu zapewnia syntantycznie inny sposób iteracji po liczbach całkowitych.

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

Rob Pike (autor Go) skrytykował to :

Wygląda na to, że prawie za każdym razem, gdy ktoś wymyśla sposób, aby uniknąć zrobienia czegoś takiego jak pętla for w idiomatyczny sposób, ponieważ wydaje się ona zbyt długa lub uciążliwa, rezultatem jest prawie zawsze więcej naciśnięć klawiszy niż rzecz, która jest rzekomo krótsza. […] To pomijając wszystkie szalone koszty, jakie przynoszą te „ulepszenia”.


16
Krytyka Pike'a jest uproszczona, ponieważ odnosi się tylko do naciśnięć klawiszy, a nie do mentalnego narzutu ciągłego ponownego deklarowania zakresów. Ponadto w przypadku większości współczesnych edytorów iterwersja faktycznie wykorzystuje mniej naciśnięć klawiszy, ponieważ rangei iterbędzie się autouzupełnianie.
Chris Redford,

1
@ lang2, forpętle nie są obywatelami Uniksa pierwszej klasy, tak jak są w ruchu. Poza tym, w przeciwieństwie do for, seqstrumienie mocy standardowej sekwencji liczb. Od konsumenta zależy, czy będzie je powtarzał. Chociaż for i in $(seq 1 10); do ... done jest to powszechne w Shell, jest to tylko jeden sposób na wykonanie pętli for, która sama w sobie jest tylko jednym ze sposobów wykorzystania danych wyjściowych seq, aczkolwiek bardzo powszechnym.
Daniel Farrell

2
Ponadto Pike po prostu nie bierze pod uwagę faktu, że kompilacja (biorąc pod uwagę specyfikację języka obejmowała składnię zakresu dla tego przypadku użycia) mogłaby zostać zbudowana w taki sposób, aby traktować ją i in range(10)dokładnie tak samo i := 0; i < 10; i++.
Rouven B.

8

Oto punkt odniesienia do porównania forinstrukcji Go z instrukcją ForClause i Go rangeprzy użyciu iterpakietu.

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
}

Wynik:

$ 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
Jeśli ustawisz pętle na 10, a następnie powtórz test porównawczy, zobaczysz wyraźną różnicę. Na moim komputerze ForClause zajmuje 5,6 ns, podczas gdy Iter zajmuje 15,4 ns, więc wywołanie alokatora (nawet jeśli jest wystarczająco sprytne, aby niczego nie alokować) nadal kosztuje 10 ns i całą stertę dodatkowego kodu do niszczenia pamięci podręcznej I.
Nick Craig-Wood

Byłbym zainteresowany, aby zobaczyć testy porównawcze i krytykę pakietu, który utworzyłem i do którego odwoływałem się w mojej odpowiedzi .
Chris Redford,

5

Chociaż współczuję ci z powodu braku tej funkcji języka, prawdopodobnie będziesz chciał użyć zwykłej forpętli. I prawdopodobnie będziesz z tym bardziej w porządku, niż myślisz, pisząc więcej kodu w Go.

Napisałem ten pakiet iterowy - który jest wspierany przez prostą, idiomatyczną forpętlę, która zwraca wartości ponad a chan int- w celu ulepszenia projektu znalezionego na https://github.com/bradfitz/iter , który, jak wskazano, ma problemy z buforowaniem i wydajnością, a także sprytna, ale dziwna i nieintuicyjna implementacja. Moja własna wersja działa w ten sam sposób:

package main

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

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

Jednak analiza porównawcza ujawniła, że ​​korzystanie z kanału było bardzo kosztowną opcją. Porównanie 3 metod, które można uruchomić iter_test.gow moim pakiecie przy użyciu

go test -bench=. -run=.

określa ilościowo, jak słaba jest jego wydajność

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

W tym procesie ten test porównawczy pokazuje również, w jaki sposób bradfitzrozwiązanie ma gorsze wyniki w porównaniu z wbudowaną forklauzulą ​​dla pętli o rozmiarze 10.

Krótko mówiąc, wydaje się, że do tej pory nie odkryto żadnego sposobu na zduplikowanie wydajności wbudowanej forklauzuli, zapewniając jednocześnie prostą składnię [0,n)podobną do tej, którą można znaleźć w Pythonie i Ruby.

Szkoda, ponieważ prawdopodobnie zespołowi Go byłoby łatwo dodać prostą regułę do kompilatora, aby zmienić wiersz, taki jak

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

do tego samego kodu maszynowego co for i := 0; i < 10; i++.

Jednak, żeby być uczciwym, po napisaniu własnego iter.N(ale przed testem porównawczym) wróciłem do niedawno napisanego programu, aby zobaczyć wszystkie miejsca, w których mogłem go użyć. Właściwie nie było ich wiele. Było tylko jedno miejsce w nieistotnej sekcji mojego kodu, w którym mogłem obejść się bez bardziej kompletnej, domyślnej forklauzuli.

Więc chociaż może to wyglądać na ogromne rozczarowanie dla języka w zasadzie, może się okazać - tak jak ja - że tak naprawdę nie potrzebujesz go w praktyce. Tak jak Rob Pike mówi o produktach generycznych, możesz nie przegapić tej funkcji tak bardzo, jak myślisz.


1
Używanie kanału do iteracji jest bardzo kosztowne; Gorutyny i kanały są tanie, nie są darmowe. Jeśli iteracyjny zakres w kanale kończy się wcześnie, goroutine nigdy się nie kończy (wyciek gorutyny). Metoda Iter została usunięta z pakietu wektorowego . „ kontener / wektor: usuń Iter () z interfejsu (Iter () prawie nigdy nie jest właściwym mechanizmem do wywołania). ” Twoje rozwiązanie iterowe jest zawsze najdroższe.
peterSO

4

Jeśli chcesz po prostu iterować w zakresie bez użycia i indeksów lub czegokolwiek innego, ten przykład kodu działał dobrze dla mnie. Nie potrzeba dodatkowej deklaracji, nie _. Nie sprawdzałem jednak wydajności.

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

PS Pierwszy dzień w GoLang. Proszę, krytykujcie, jeśli jest to złe podejście.


Jak dotąd (wersja 1.13.6) nie działa w. Rzuca się non-constant array boundwe mnie.
WHS

1

Możesz również sprawdzić github.com/wushilin/stream

Jest to leniwy strumień podobny do koncepcji 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()

Mam nadzieję że to pomoże


0
package main

import "fmt"

func main() {

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

1
Dodaj kontekst do swojego kodu, aby pomóc przyszłym czytelnikom lepiej zrozumieć jego znaczenie.
Grant Miller,

3
co to jest? suma nie jest zdefiniowana.
naftalimich

0

Napisałem pakiet w języku Golang, który naśladuje funkcję zakresu Pythona:

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

Uwaga: napisałem dla zabawy! Przy okazji, czasami może to być pomocne

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.