Bir dizi tamsayı yinelemenin bir yolu var mı?


175

Go'nun menzili haritalar ve dilimleri tekrarlayabilir, ancak bir dizi sayı üzerinde yineleme yapmanın bir yolu olup olmadığını merak ediyordum:

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

Veya Go'daki Ruby sınıfıyla nasıl bir tam sayı aralığını temsil etmenin bir yolu var ?

Yanıtlar:


225

Sadece bir for döngüsü yazabilir ve yazmalısınız. Basit, açık kod Git yoludur.

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

269
Çoğu insanın bu üç ifadeli sürümü @Vishnu'nun yazdıklarından daha basit olarak adlandıracağını sanmıyorum. Sadece belki yıllar ve yıllar sonra C veya Java aşılamasından sonra ;-)
Thomas Ahle

12
IMO, her zaman for döngüsünün bu üç ifadeli sürümüne sahip olacağınızdır (yani onunla çok daha fazlasını yapabilirsiniz, OP'nin sözdizimi sadece bir sayı aralığının bu daha kısıtlı durumu için iyidir, bu yüzden herhangi bir dilde bu genişletilmiş sürümü isteyeceksiniz) ve yeterince aynı görevi yerine getirir ve yine de oldukça farklı değildir, bu yüzden neden başka bir sözdizimini öğrenmek / hatırlamak zorundasınız. Büyük ve karmaşık bir projeyi kodluyorsanız, bir döngü kadar basit bir şey için çeşitli sözdizimleri hakkında derleyiciyle savaşmak zorunda kalmadan endişelenmeniz yeterli.
Brad Peabody

3
@ThomasAhle özellikle C ++ 'ı göz önünde bulundurarak resmi olarak boost şablon kütüphanesinden esinlenen for_each (x, y) için gösterim ekliyor
don

5
@BradPeabody bu aslında bir tercih meselesi. Python 3-ifade döngüsüne sahip değildir ve iyi çalışır. Birçoğu for-her sözdizimini çok daha az hataya meyilli olarak görür ve bu konuda özünde yetersiz bir şey yoktur.
VinGarcia

3
@necromancer, Rob Pike'ın cevabımla aynı şeyi savunan bir gönderi. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Go topluluğu aynı fikirde olmayabilir, ancak dilin yazarlarından biriyle aynı fikirde olduğunda, bu sorunun cevabı o kadar da kötü olamaz.
Paul Hankin

43

İşte şimdiye kadar önerilen iki yolu karşılaştırmak için bir program

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

Sökme oluşturmak için böyle derleyin

go build -gcflags -S iter.go

İşte sade (listeden olmayan talimatları kaldırdım)

kurmak

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

döngü

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     ,

Ve işte with_iter

kurmak

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

döngü

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     ,

Böylece, kurulum aşamasında tamamen satır içi olmasına rağmen, iter çözümünün çok daha pahalı olduğunu görebilirsiniz. Döngü aşamasında döngüde fazladan bir talimat vardır, ancak bu çok da kötü değildir.

Ben döngü için basit kullanırdım.


8
“İter çözümün çok daha pahalı olduğunu göremiyorum”. Go sözde birleştirici talimatlarını sayma yönteminiz kusurludur. Bir kıyaslama yapın.
peterSO

11
Bir çözüm çağırıyor runtime.makesliceve diğeri değil - bunun çok daha yavaş olacağını bilmek için bir kıyaslamaya ihtiyacım yok!
Nick Craig-Wood

6
Evet runtime.makeslice, sıfır boyut tahsisi istiyorsanız, bellek ayırmayacak kadar zekidir. Ancak yukarıdakiler hala onu çağırıyor ve kıyaslamanıza göre makinemde 10nS daha uzun sürüyor.
Nick Craig-Wood

4
Bu, performans nedenleriyle C ++ üzerinden C ++ kullanmayı öneren kişilere hatırlatıyor
necromancer

5
Goland'da yaygın olsa da, nanosaniye CPU işlemlerinin çalışma zamanı performansını tartışmak bana aptalca geliyor. Okunabilirlikten sonra çok uzak bir son husus olduğunu düşünürdüm. CPU performansı alakalı olsa bile, for döngüsünün içeriği neredeyse her zaman döngünün kendisi tarafından yapılan farkları değiştirecektir.
Jonathan Hartley

34

Mark Mishyn tarafından dilim kullanılması önerildi, ancak değişmez yolla oluşturulan dizi kullanılabildiğinde ve daha kısa olduğunda dizi oluşturmak makeve fordöndürülen dilimde kullanmak için bir neden yoktur.

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

8
Değişkeni kullanmayacaksanız, sol tarafı da atlayabilir ve kullanabilirsinizfor range [5]int{} {
blockloop

6
Dezavantajı 5burada bir değişmezdir ve çalışma zamanında belirlenemez.
Steve Powell

Döngü için normal üç ifadeyle daha hızlı mı yoksa karşılaştırılabilir mi?
Amit Tripathi

@AmitTripathi evet, karşılaştırılabilir, milyarlarca yineleme için yürütme süresi neredeyse aynı.
Daniil Grankin

18

iter , tamsayılar üzerinde yineleme yapmak için sözdizimsel olarak farklı bir yol sağlayan çok küçük bir pakettir.

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

Rob Pike (Go'nun yazarı) eleştirdi :

Görünüşe göre, neredeyse her biri bir for döngüsü gibi bir şey yapmaktan kaçınmak için bir yol bulduğunda, çok uzun veya hantal hissettirdiğinden, sonuç neredeyse her zaman daha kısa olandan daha fazla tuş vuruşudur. [...] Bu "iyileştirmeler" getirmek tüm çılgın yükü bir kenara bırakıyor.


16
Pike'ın eleştirisi, sürekli tekrarlanan aralıkların zihinsel yükünden ziyade sadece tuş vuruşlarını ele alması nedeniyle basittir. Ayrıca, en modern editörleri ile, iterçünkü sürüm aslında daha az tuş vuruşlarını kullanan rangeve iterotomatik olarak tamamlayacaktır.
Chris Redford

1
@ lang2, fordöngüler gittikleri gibi Unix'in birinci sınıf vatandaşı değil. Ayrıca, aksine for, seqstandart çıktı akışları bir sayı dizisi. Bunları yinelemek veya istememek tüketiciye bağlıdır. for i in $(seq 1 10); do ... done Shell'de yaygın olmasına rağmen , for döngüsü yapmanın sadece bir yolu, ki bu seqda çok yaygın olsa da , çıktıyı tüketmenin tek yoludur .
Daniel Farrell

2
Ayrıca, Pike, bir derlemenin (bu özellik örneği için bir dil sözdizimi içerdiği göz önüne alındığında) bir derlemenin i in range(10)tam olarak aynı şekilde davranılacak şekilde oluşturulabileceğini düşünmüyor i := 0; i < 10; i++.
Rouven B.

8

Paketi kullanarak forbir Go rangeifadesini bir ForClause ve bir Go ifadesiyle karşılaştırmak için bir karşılaştırma ölçütü 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
}

Çıktı:

$ 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
Döngüleri 10 olarak ayarlarsanız, karşılaştırmalı değerlendirmeyi yeniden deneyin, belirgin bir fark görürsünüz. Makinemde ForClause 5.6 ns alırken, Iter 15.4 ns alır, bu yüzden ayırıcıyı çağırmak (hiçbir şey ayırmayacak kadar akıllı olsa bile) hala 10ns ve ekstra I-cache busting kodunun bir yığınına mal olur.
Nick Craig-Wood

Cevabımda oluşturduğum ve referans verdiğim paketle ilgili kriterlerinizi ve eleştirilerinizi görmek isterim .
Chris Redford

5

Bu dil özelliğinden yoksun olmanızla ilgili endişelerinizi kabul ederken, muhtemelen normal bir fordöngü kullanmak isteyeceksiniz . Ve muhtemelen daha fazla Go kodu yazarken düşündüğünüzden daha iyi olacaksınız.

Yazdığım bu iter paketi bir basit tarafından desteklenen, deyimsel - fordöner değerler üzerinde bu döngü chan int- çabasıyla tasarım bulundu geliştirmek https://github.com/bradfitz/iter olması dikkat çekti edildiği, önbellek ve performans sorunlarının yanı sıra zeki, ancak garip ve sezgisel olmayan bir uygulama. Kendi sürümüm aynı şekilde çalışıyor:

package main

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

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

Ancak, kıyaslama kanal kullanımının çok pahalı bir seçenek olduğunu ortaya koydu. iter_test.goKullanarak paketimden çalıştırılabilecek 3 yöntemin karşılaştırılması

go test -bench=. -run=.

performansının ne kadar zayıf olduğunu ölçüyor

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

Süreçte, bu karşılaştırma ölçütü, bradfitzçözümün fordöngü boyutu için yerleşik maddeye kıyasla nasıl düşük performans gösterdiğini de gösterir 10.

Kısacası, Python ve Ruby'de bulunanlar foriçin basit bir sözdizimi sağlarken , yerleşik cümlenin performansını çoğaltmanın hiçbir yolu [0,n)bulunamamıştır.

Bu utanç verici çünkü Go ekibinin derleyiciye basit bir kural eklemesi,

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

ile aynı makine koduna for i := 0; i < 10; i++.

Ancak, adil olmak iter.Ngerekirse, kendim yazdıktan sonra (ama kıyaslamadan önce), kullanabileceğim tüm yerleri görmek için yakın zamanda yazılmış bir programa geri döndüm. Aslında pek yoktu. Kodumun hayati olmayan bir bölümünde daha eksiksiz, varsayılan bir formadde olmadan alabileceğim tek bir nokta vardı .

Bu, prensipte dil için büyük bir hayal kırıklığı gibi görünse de, tıpkı benim yaptığım gibi - aslında pratikte buna gerçekten ihtiyacınız olmadığını görebilirsiniz. Rob Pike'ın jenerikler için söylediği gibi, aslında bu özelliği düşündüğünüz kadar kaçırmayabilirsiniz.


1
Yineleme için bir kanal kullanmak çok pahalıdır; Goroutines ve kanalları ucuz, ücretsiz değil. Kanal üzerindeki yinelemeli aralık erken sona ererse, goroutin asla bitmez (bir goroutin sızıntısı). Iter yöntemi vektör paketinden silindi . " container / vector: Iter () 'i arabirimden kaldır (Iter () hemen hemen hiçbir zaman çağrılacak doğru mekanizma değildir). " iter çözümünüz her zaman en pahalıdır.
peterSO

4

Kullanmadan ve endeksler ya da başka bir şey olmadan bir aralıkta yineleme yapmak istiyorsanız, bu kod örneği benim için iyi çalıştı. Ek bir bildiriye gerek yok, hayır _. Yine de performansı kontrol etmedim.

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

PS: GoLang'ın ilk günü. Lütfen, yanlış bir yaklaşım olup olmadığını eleştirin.


Şimdiye kadar (sürüm 1.13.6), çalışmıyor. non-constant array boundBana atıyor .
WHS

1

Ayrıca github.com/wushilin/stream adresine de bakabilirsiniz.

Java.util.stream kavramı gibi tembel bir dere.

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

Bu yardımcı olur umarım


0
package main

import "fmt"

func main() {

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

1
Gelecekteki okuyucuların anlamını daha iyi anlamalarına yardımcı olmak için kodunuza bir bağlam ekleyin.
Grant Miller

3
bu nedir? toplam tanımlanmamış.
naftalimich

0

Golang'da Python'un aralık işlevini taklit eden bir paket yazdım:

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
}

Not: Eğlenmek için yazdım! Btw, bazen yardımcı olabilir

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.