整数の範囲を反復する方法はありますか?


175

Goの範囲はマップとスライスを反復処理できますが、次のような数値の範囲を反復処理する方法があるかどうか疑問に思っていました。

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

または、RubyがRangeクラスで行うようにGoで整数の範囲を表す方法はありますか?

回答:


225

forループを作成できます。シンプルでわかりやすいコードがGoの方法です。

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

269
ほとんどの人は、この3つの式のバージョンを@Vishnuが書いたものよりも単純だとは思わないでしょう。おそらく、何年にも
Thomas

12
IMOの要点は、forループのこの3つの式バージョンを常に使用することです(つまり、これを使用してさらに多くのことができます。OPの構文は、数値範囲のより制限された場合にのみ有効であるため、どの言語でも、この拡張バージョンが必要になります)、同じタスクを十分に実行でき、それでもそれほど大きな違いはないので、なぜ別の構文を学習/記憶する必要がありますか。大規模で複雑なプロジェクトをコーディングしている場合は、ループのような単純なもののさまざまな構文についてコンパイラーと戦わなくても、すでに心配する必要があります。
Brad Peabody

3
@ThomasAhle特に公式ブーストテンプレートライブラリに触発表記for_each(x、y)を追加してC ++を考慮
明るいドン

5
@BradPeabodyこれは実際には好みの問題です。Pythonには3式ループがなく、正常に動作します。多くの人は、for-each構文の方がエラーが発生しにくいと考えており、本質的に非効率的なものはありません。
VinGarcia 2017年

3
@necromancerは、私の答えとほとんど同じことを主張するRob Pikeからの投稿です。groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J。Goコミュニティは同意しないかもしれませんが、言語の作成者の1人と同意する場合、それほど悪い答えにはなりません。
ポール・ハンキン

43

これは、これまでに提案された2つの方法を比較するプログラムです

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

このようにコンパイルして逆アセンブリを生成します

go build -gcflags -S iter.go

これは明白です(私はリストから非指示を削除しました)

セットアップ

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

ループ

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     ,

そしてここにwith_iterがあります

セットアップ

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

ループ

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     ,

したがって、セットアップフェーズで完全にインライン化されていても、iterソリューションの方がかなり高価であることがわかります。ループ段階では、ループ内に余分な命令がありますが、それほど悪くはありません。

単純なforループを使用します。


8
「iterソリューションの方がかなり高価であることがわかりません」。Go疑似アセンブラー命令をカウントする方法に欠陥があります。ベンチマークを実行します。
peterSO 2014

11
1つのソリューションは呼び出しruntime.makeslice、もう1つは呼び出しません-それがずっと遅くなることを知るのにベンチマークは必要ありません!
Nick Craig-Wood、

6
はいruntime.makesliceあなたはゼロサイズの割り当てを要求した場合に任意のメモリを割り当てるためではない賢い十分です。しかし、上記はまだそれを呼び出しており、あなたのベンチマークによれば、私のマシンでは10nS長くかかります。
Nick Craig-Wood、

4
これは、パフォーマンス上の理由からC ++ではなくCを使用することを提案している人々を思い出させます
ネクロマンサー2018年

5
ゴランドでは一般的ですが、ナノ秒のCPUオペレーションのランタイムパフォーマンスについて議論することは、私にはばかげているようです。私は、読みやすさの後、非常に遠い最後の検討事項だと思います。CPUのパフォーマンスが適切であったとしても、forループの内容は、ほとんどの場合、ループ自体によって生じたあらゆる相違を圧倒します。
ジョナサンハートレー

34

Mark Mishynがスライスを使用することを提案しましたが、リテラルを介して作成された配列を使用でき、それが短い場合はmakefor返されたスライスで配列を作成して使用する理由はありません。

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

8
変数を使用しない場合は、左側を省略して使用することもできますfor range [5]int{} {
blockloop

6
欠点は、5ここがリテラルであり、実行時に決定できないことです。
スティーブパウエル

ループの場合、通常の3つの式よりも高速ですか、それとも同等ですか?
Amit Tripathi

@AmitTripathiはい、同等です。実行時間は数十億回の反復でほぼ同じです。
Daniil Grankin

18

iterは、整数を反復処理するための構文的に異なる方法を提供する非常に小さなパッケージです。

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

Rob Pike(Goの作者)はそれを批判しました

誰かがforループのようなことをするのを避ける方法を思いつくたびに、それはあまりにも長く感じたり面倒だと感じたりするので、ほとんど常に、おそらく短いものよりもキーストロークが多くなります。[...]それは、これらの「改善」がもたらすクレイジーなオーバーヘッドをすべて残しています。


16
パイクの批評は、範囲を絶えず再宣言するという精神的なオーバーヘッドではなく、キーストロークのみに対処するという点で単純化しています。また、ほとんどの最新のエディターでは、iterバージョンがオートコンプリートするためrange、実際には使用するキーストロークが少なくなりiterます。
Chris Redford

1
@ lang2、forループは、Unixの第一級市民ではありません。さらに、とは異なりforseq標準へのストリームは一連の数値を出力します。それらを反復するかどうかは、消費者次第です。for i in $(seq 1 10); do ... done シェルでは一般的ですが、それはforループを実行する1つの方法にすぎません。これはseq、非常に一般的な方法ですが、それ自体がの出力を消費する1つの方法にすぎません。
Daniel Farrell

2
また、Pikeは、(言語仕様にこのユースケースの範囲構文が含まれている場合)コンパイルがとi in range(10)まったく同じように構築される可能性があるという事実を単に考慮していませんi := 0; i < 10; i++
ルーベンB.

8

これは、パッケージを使用してforGo rangeステートメントをForClauseおよびGo ステートメントと比較するためのベンチマーク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
}

出力:

$ 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
ループを10に設定した場合、ベンチマークを再試行すると、マークされた違いが表示されます。私のマシンでは、ForClauseは5.6 nsかかりますが、Iterは15.4 nsかかります。そのため、アロケータを呼び出すと(それは何も割り当てないほど十分に賢明ですが)、依然として10nsかかり、余分なIキャッシュ無効化コードのヒープ全体がかかります。
Nick Craig-Wood

私が作成し、私の回答で参照したパッケージのベンチマークと批評を見てみたいと思います
Chris Redford

5

私はこの言語機能の欠如についての懸念を表明しますが、おそらく通常のforループを使用したいと思うでしょう。そして、Goコードを書くほど、あなたは思ったよりも大丈夫でしょう。

私が書いたこのITERパッケージシンプルに裏打ちされた、慣用- for戻り値を超えるというループをchan int-試みでは、デザインがで見つかった上で改善することがhttps://github.com/bradfitz/iter持つことが指摘されています、キャッシングとパフォーマンスの問題、そして賢いが奇妙で直感的でない実装。私自身のバージョンも同じように動作します:

package main

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

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

ただし、ベンチマークにより、チャネルの使用は非常に高価なオプションであることが明らかになりました。iter_test.goを使用してパッケージで実行できる3つのメソッドの比較

go test -bench=. -run=.

そのパフォーマンスがどれだけ悪いかを数値化する

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

このプロセスでは、このベンチマークは、ループサイズがのbradfitz組み込みfor句と比較して、ソリューションのパフォーマンスがどのように低いかを示しています10

要するに、PythonやRubyに見られるようforな単純な構文を提供しながら、組み込みの句のパフォーマンスを複製する方法は今のところ発見されていないよう[0,n)です。

Goチームがコンパイラに簡単なルールを追加して次のような行を変更するのはおそらく簡単なので、これは残念です。

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

と同じマシンコードにfor i := 0; i < 10; i++

しかし、公平を期すために、自分で作成した後iter.N(ただしベンチマークの前)、最近作成したプログラムに戻って、使用できるすべての場所を確認しました。実際には多くありませんでした。私のコードの重要ではないセクションには、より完全なデフォルトのfor句なしで済む場所が1つしかありませんでした。

だから、これは原則として言語にとって大きな失望のように見えるかもしれませんが、私がしたように、実際には実際にはそれを必要としないかもしれません。Rob Pikeがジェネリックスについて言っていることで知られているように、実際にこの機能を見逃すことはないでしょう。


1
反復にチャネルを使用すると、非常にコストがかかります。goroutinesとチャネルは安く、無料ではありません。チャネルの反復範囲が早く終了した場合、ゴルーチンは終了しません(ゴルーチンリーク)。Iterメソッドはベクターパッケージから削除されました。" コンテナー/ベクター:インターフェイスからIter()を削除します(Iter()が呼び出すのに適切なメカニズムになることはほとんどありません)。 " iterソリューションは常に最も高価です。
peterSO

4

インデックスやその他を使用せずに範囲を反復処理する場合は、このコードサンプルがうまく機能しました。追加の宣言は必要ありません_。ただし、パフォーマンスは確認していません。

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

PS GoLangの最初の日。それが間違ったアプローチである場合は、批評してください。


これまでのところ(バージョン1.13.6)、動作しません。non-constant array bound私を投げます。
WHS

1

github.com/wushilin/streamもご覧ください。

これは、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()

お役に立てれば


0
package main

import "fmt"

func main() {

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

1
コードにコンテキストを追加して、将来の読者がその意味をよりよく理解できるようにします。
Grant Miller、

3
これは何ですか?合計が定義されていません。
naftalimich

0

Pythonのrange関数を模倣するパッケージをGolangで作成しました。

パッケージ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
}

注:私は楽しみのために書きました!ところで、時にはそれが役立つかもしれません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.