Is there a way to iterate over a range of integers?
Asked Answered
W

15

295

Go's range can iterate over maps and slices, but I was wondering if there is a way to iterate over a range of numbers, something like this:

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

Or is there a way to represent range of integers in Go like how Ruby does with the class Range?

Wystand answered 22/2, 2014 at 5:39 Comment(1)
Same question but about C++ (and originally from 2013), just for comparative inspiration.Rowdyism
D
395

From Go 1.22 (expected release February 2024), you will be able to write:

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

(ranging over an integer in Go iterates from 0 to one less than that integer).

For versions of Go before 1.22, the idiomatic approach is to write a for loop like this.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
Dither answered 22/2, 2014 at 7:15 Comment(14)
I don't think most people would call this three-expression version more simple than what @Wystand wrote. Only perhaps after years and years of C or Java indoctrination ;-)Haworth
IMO the point is that you are always going to have this three-expression version of the for loop (i.e. you can do a lot more with it, the syntax from the OP is only good for that more restricted case of a number range, so in any language you're going to want this extended version) and it sufficiently accomplishes the same task, and isn't remarkably different anyway, so why have to learn/remember another syntax. If you are coding on a large and complex project you have enough to worry about already without having to fight the compiler about various syntaxes for something as simple as a loop.Pontificate
@ThomasAhle especially considering C++ is officially adding notation for_each(x,y) inspired by the boost template libraryTwicetold
@BradPeabody this is actually a matter of preference. Python does not have the 3-expression loop and works fine. Many consider the for-each syntax a lot less error-prone and there is nothing intrinsically inefficient about it.Chromatism
-1, sorry -n for let me count the ways this is awful: suppose you need to rename i, you have to now do so in 2 more places than otherwise. If something changes the value of i within the loop, you're screwed. And this truly demonstrates awful taste -- if you must write this monstrosity the correct way to do so is to index from 0 not 1 and use i < 10 rather than i <= 10. Now excluse me while I go puke, not so much for this bad answer but more for the fact that the OP accepted it. yuck! PS: @ThomasAhleLiebowitz
actually the worst part is the presentation of this as the most superior "can, and should" + "Simple, obvious code" + "is the Go way" -- I doubt if much of the Go community considers this kind of stuff as "the Go way".Liebowitz
@Liebowitz here is a post from Rob Pike arguing for much the same thing as my answer. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . It may be that the Go community disagrees, but when it agrees with one of the authors of the language, it can't really be that bad of an answer.Dither
@PaulHankin That entirely depends upon whether you view coding as a science or as a religion. Rob is unfortunately mistaken and then defensively power-tripping here, and you are blindly channeling his hand-it-down. For your own engineering benefit, don't dismiss that changing the value of i within Rob's loop causes it to self-destruct, whereas the range approach is immune. And, "DRY" is a universally accepted language-design principle, and you can see i 3 times in Rob's loop! Anyway, now look at me repeating myself. I'd treat each upvote of the first comment as a polite downvote.Liebowitz
While the "normal" for-loop is clearly going to win out, nothing can beat the compiler supporting it directly. For example, if I want a dynamic range then:Religion
for i:=1; i<len(a_slice); i++ {...} evaluates the terminating conditional expression len(a_slice) several times (as well as allowing i to be updated in the loop) which obviates optimisation in the loop body. A native, compiler idiom would allow the compiler to evaluate the range bounds once, capitalise on simple increments, and check for potential rounding or overflow once. I doubt if anyone could match that. Considering the syntax is easier and quicker to read (have you missed the end points?). Why doesn't Go allow it. Admittedly there are few places where it is needed.Religion
Lots of over thinking for this I believe for a, _ := range [10]int {} { // do something with a ignore index } nvm this is pointed out belowDendriform
The elitism of "the Go way" never ceases to amaze. I'm surprised the answer here wasn't "just write a function for it", it almost always seems to be.Mcnary
To defend Paul, the OP asked for idiomatic. The question didn't involve philosophy. Paul is simply adding background about the language designers. I don't personally always love Go's approach, but there is a valid reason Rob Pike designed it the way he did. If you've ever had to deal with a real-world code base with 100 different approaches...Lemke
Now that go 1.22 has been released, it's only a matter of time before people start asking how you make for i := range n go up by a certain increment, or how you make it go in reverse order.Allomorphism
S
65

It was suggested by Mark Mishyn to use slice but there is no reason to create array with make and use in for returned slice of it when array created via literal can be used and it's shorter

for i := range [5]int{} {
        fmt.Println(i)
}
Singlestick answered 7/12, 2017 at 21:34 Comment(7)
If you're not going to use the variable you can also omit the left side and use for range [5]int{} {Osullivan
Drawback is that 5 here is a literal and cannot be determined at run-time.Religion
Is it faster or comparable to normal three expressions for loop?Gilded
@AmitTripathi yes, it's comparable, execution time is almost the same for billions of iterations.Singlestick
Looks nice, but does that use O(n) memory though?Creaturely
range [5][0]int{} then no memory will be allocated i thinkPopcorn
[5]struct{}{} won't allocate any memory. I am unsure about [5][0]int{}Tigon
P
57

Here is a program to compare the two ways suggested so far

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

Compile like this to generate disassembly

go build -gcflags -S iter.go

Here is plain (I've removed the non instructions from the listing)

setup

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     ,

And here is with_iter

setup

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     ,

So you can see that the iter solution is considerably more expensive even though it is fully inlined in the setup phase. In the loop phase there is an extra instruction in the loop, but it isn't too bad.

I'd use the simple for loop.

Picker answered 22/2, 2014 at 8:50 Comment(7)
I can't "see that the iter solution is considerably more expensive." Your method of counting Go pseudo-assembler instructions is flawed. Run a benchmark.Shoop
One solution calls runtime.makeslice and the other doesn't - I don't need a benchmark to know that is going to be a lot slower!Picker
I've revised my benchmark to clearly show the flaws in your argument; there are zero memory allocations per operation. If you are still puzzled, let me know, and I'll provide a detailed explanation of what Go is doing.Shoop
Yes runtime.makeslice is clever enough not to allocate any memory if you ask for zero size allocation. However the above still calls it, and according to your benchmark does take 10nS longer on my machine.Picker
I would be interested to see your benchmarks and critiques for the package I created and referenced in my answer.Nedanedda
this reminds of people suggesting to use C over C++ for performance reasonsLiebowitz
Debating the runtime performance of nanosecond CPU operations, while common in Goland, seems silly to me. I'd consider that a very distant last consideration, after readability. Even if CPU performance were relevant, the contents of the for loop will almost always swamp whatever differences incurred by the loop itself.Rollin
B
23

iter is a very small package that just provides a syntantically different way to iterate over integers.

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

Rob Pike (an author of Go) has criticized it:

It seems that almost every time someone comes up with a way to avoid doing something like a for loop the idiomatic way, because it feels too long or cumbersome, the result is almost always more keystrokes than the thing that is supposedly shorter. [...] That's leaving aside all the crazy overhead these "improvements" bring.

Beckett answered 22/2, 2014 at 5:44 Comment(4)
Pike's critique is simplistic in that it only addresses the keystrokes rather than the mental overhead of constantly redeclaring ranges. Also, with most modern editors, the iter version actually uses fewer keystrokes because range and iter will autocomplete.Nedanedda
@lang2, for loops are not a first class citizen of Unix like they are in go. Besides, unlike for, seq streams to standard output a sequence of numbers. Whether or not to iterate over them is up to the consumer. Though for i in $(seq 1 10); do ... done is common in Shell, it's only one way to do a for loop, which is itself only one way to consume the output of seq, albeit a very common one.Simplistic
Also, Pike simply doesn't consider the fact that a compile (given the language specs included a range syntax for this use case) could be build in a way to just treat i in range(10) exactly like i := 0; i < 10; i++.Piton
FYI: “This package [the ‘iter’ package] was intended to be an educational joke when it was released in 2014. People didn’t get the joke part and started depending on it. That’s fine, I guess. (This is the Internet.) But it’s kinda weird. It’s one line, and not even idiomatic Go style.”Hennebery
S
9

Here's a benchmark to compare a Go for statement with a ForClause and a Go range statement using the iter package.

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
}

Output:

$ 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
$
Shoop answered 22/2, 2014 at 18:23 Comment(2)
If you set loops to 10 then retry the benchmark you'll see a marked difference. On my machine the ForClause takes 5.6 ns whereas the Iter takes 15.4 ns, so calling the allocator (even though it is clever enough not to allocate anything) still costs 10ns and a whole heap of extra I-cache busting code.Picker
I would be interested to see your benchmarks and critiques for the package I created and referenced in my answer.Nedanedda
B
8

If you want to just iterate over a range w/o using and indices or anything else, this code sample worked just fine for me. No extra declaration needed, no _. Haven't checked the performance, though.

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

P.S. The very first day in GoLang. Please, do critique if it's a wrong approach.

Banville answered 30/7, 2019 at 3:51 Comment(1)
N must be constant: for range [5]int{} {}Moorman
N
6

While I commiserate with your concern about lacking this language feature, you're probably just going to want to use a normal for loop. And you'll probably be more okay with that than you think as you write more Go code.

I wrote this iter package — which is backed by a simple, idiomatic for loop that returns values over a chan int — in an attempt to improve on the design found in https://github.com/bradfitz/iter, which has been pointed out to have caching and performance issues, as well as a clever, but strange and unintuitive implementation. My own version operates the same way:

package main

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

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

However, benchmarking revealed that the use of a channel was a very expensive option. The comparison of the 3 methods, which can be run from iter_test.go in my package using

go test -bench=. -run=.

quantifies just how poor its performance is

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

In the process, this benchmark also shows how the bradfitz solution underperforms in comparison to the built-in for clause for a loop size of 10.

In short, there appears to be no way discovered so far to duplicate the performance of the built-in for clause while providing a simple syntax for [0,n) like the one found in Python and Ruby.

Which is a shame because it would probably be easy for the Go team to add a simple rule to the compiler to change a line like

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

to the same machine code as for i := 0; i < 10; i++.

However, to be fair, after writing my own iter.N (but before benchmarking it), I went back through a recently written program to see all the places I could use it. There actually weren't many. There was only one spot, in a non-vital section of my code, where I could get by without the more complete, default for clause.

So while it may look like this is a huge disappointment for the language in principle, you may find — like I did — that you actually don't really need it in practice. Like Rob Pike is known to say for generics, you might not actually miss this feature as much as you think you will.

Nedanedda answered 3/4, 2017 at 18:54 Comment(1)
Using a channel for iteration is very expensive; goroutines and channels are cheap, they are not free. If the iterative range over the channel terminates early, the goroutine never ends (a goroutine leak). The Iter method was deleted from the vector package. "container/vector: remove Iter() from interface (Iter() is almost never the right mechanism to call)." Your iter solution is always the most expensive.Shoop
G
4

Go 1.22

Starting from 1.22 (February 2024) you will be able to rewrite for loops from zero to N by simply ranging over an integer. Therefore:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Becomes:

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

You can now find it mentioned in the language specification within the For-range section:

The expression on the right in the "range" clause is called the range expression, its core type must be [one of the usual cases], or an integer.

And:

For an integer value n, the iteration values 0 through n-1 are produced in increasing order, with the same type as n. If n <= 0, the loop does not run any iterations.

Note that ranging over an integer allows only one iteration variable. Clearly the idiom i, v := range isn't necessary here.

This new language feature covers only ranges in the form [0,N) — i.e. from zero to N excluded. For everything else you must still write the full three-clause loop.

Galoot answered 12/12, 2023 at 8:42 Comment(0)
K
1

I have written a package in Golang which mimic the Python's range function:

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

Note: I have written for fun! Btw, sometimes it may be helpful

Keeter answered 25/4, 2020 at 0:14 Comment(0)
V
1

Here is a compact, dynamic version that doesn't depend on iter (but works similarly):

package main

import (
    "fmt"
)

// N is an alias for an unallocated struct
func N(size int) []struct{} {
    return make([]struct{}, size)
}

func main() {
    size := 1000
    for i := range N(size) {
        fmt.Println(i)
    }
}

With some tweaks size could be of type uint64 (if needed) but that's the gist.

Viscountcy answered 25/8, 2020 at 13:0 Comment(0)
L
1

in Addition to @Blackgreen post,

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

this case also is Valid ( when you do not need counter variable(index))

for range 10 {
    doSomething()
}
Leon answered 15/4, 2024 at 13:28 Comment(0)
W
0

You can also check out github.com/wushilin/stream

It is a lazy stream like concept of 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()

Hope this helps

Whitsun answered 2/10, 2015 at 6:50 Comment(0)
E
0

The problem is not the range, the problem is how the end of slice is calculated. with a fixed number 10 the simple for loop is ok but with a calculated size like bfl.Size() you get a function-call on every iteration. A simple range over int32 would help because this evaluate the bfl.Size() only once.

type BFLT PerfServer   
  func (this *BFLT) Call() {
    bfl := MqBufferLCreateTLS(0)                                                                                   
    for this.ReadItemExists() {                                                                                    
      bfl.AppendU(this.ReadU())                                                                                    
    }
    this.SendSTART()
    // size := bfl.Size() 
    for i := int32(0); i < bfl.Size() /* size */; i++ {                                                                             
      this.SendU(bfl.IndexGet(i))                                                                                  
    }
    this.SendRETURN()
  }
Elyseelysee answered 7/11, 2020 at 8:21 Comment(0)
S
0

As answered by others, this is currently not supported in the language. Idiomatic way of Go is to use a for loop.

However, there is a recent proposal by RSC that extends the range to iterate over integers.

If n is an integer type, then for x := range n { ... } would be completely equivalent to for x := T(0); x < n; x++ { ... }, where T is the type of n (assuming x is not modified in the loop body).

I'm optimistic that this proposal will make it into the language.

Selfinduced answered 23/8, 2023 at 10:40 Comment(0)
G
-1
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}
Glabrate answered 26/7, 2018 at 2:49 Comment(2)
Add some context to your code to help future readers better understand its meaning.Iinden
what is this? sum is not defined.Hideous

© 2022 - 2025 — McMap. All rights reserved.