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?
Since integers, individualistically, are not iterable, when we try to do a for x in 7 , it raises an exception stating TypeError: 'int' object is not iterable .
for loop with range() Python for loop executes a block of code or statement repeatedly for a fixed number of times. We can iterate over a sequence of numbers produced by the range() function using for loop.
To loop through a set of code a specified number of times, we can use the range() function, The range() function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and ends at a specified number.
for loops repeat a block of code for all of the values in a list, array, string, or range() . We can use a range() to simplify writing a for loop. The stop value of the range() must be specified, but we can also modify the start ing value and the step between integers in the range() .
The idiomatic approach in Go is to write a for loop like this.
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
There's definitely advantages in ranges and they're used in many other languages, but a Go design principle is to only introduce an abstraction if the benefits significantly outweigh the costs (including the cost of making the language larger). Reasonable people disagree about the costs and benefits of ranges, but this answer is my attempt to describe what I think idiomatic Go is.
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.
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)
}
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.
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
$
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.
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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With