ChatGPT解决这个技术问题 Extra ChatGPT

Is there a way to iterate over a range of integers?

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?

Same question but about C++ (and originally from 2013), just for comparative inspiration.

P
Paul Hankin

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.


I don't think most people would call this three-expression version more simple than what @Vishnu wrote. Only perhaps after years and years of C or Java indoctrination ;-)
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.
@ThomasAhle especially considering C++ is officially adding notation for_each(x,y) inspired by the boost template library
@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.
@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.
N
Nick Craig-Wood

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.


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.
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!
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.
this reminds of people suggesting to use C over C++ for performance reasons
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.
D
Daniil Grankin

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

If you're not going to use the variable you can also omit the left side and use for range [5]int{} {
Drawback is that 5 here is a literal and cannot be determined at run-time.
Is it faster or comparable to normal three expressions for loop?
@AmitTripathi yes, it's comparable, execution time is almost the same for billions of iterations.
C
Chris Martin

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.


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.
@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.
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++.
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.”
p
peterSO

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 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.
I would be interested to see your benchmarks and critiques for the package I created and referenced in my answer.
W
WHS

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.


So far (version 1.13.6), it doesn't work at. Throwing non-constant array bound at me.
N must be constant: for range [5]int{} {}
C
Chris Redford

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.


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.
S
Siu Ching Pong -Asuka Kenji-

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


S
Saddam Hossain

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


M
Marco Chiappetta

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.


A
Andreas Otto

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

R
RobJan
package main

import "fmt"

func main() {

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

Add some context to your code to help future readers better understand its meaning.
what is this? sum is not defined.