是否有一种方法可以迭代一系列整数?

Go的范围可以在地图和切片上迭代,但我想知道是否有一种方法可以在数字范围上迭代,就像这样:

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

或者是否有一种方法可以在Go中表示整数的范围,就像Ruby对类范围所做的那样?

232789 次浏览

iter是一个非常小的包,它只是提供了一种语法上不同的方法来遍历整数。

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

Rob Pike (Go的作者)批评过它:

似乎每次都有人想出逃避的办法 用习惯的方式做一个for循环,因为它感觉 太长或太麻烦,结果几乎总是更多的按键 比那个更短的东西。[…这还不考虑这些“改进”带来的疯狂开销。

Go中的惯用方法是这样写一个for循环。

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

在范围上肯定有优势,它们也被用于许多其他语言中,但Go的设计原则是,只有在收益显著超过成本(包括使语言更大的成本)时才引入抽象。理性的人不同意范围的成本和收益,但这个答案是我试图描述我认为的惯用围棋是什么。

下面是一个程序来比较目前建议的两种方法

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循环。

下面是一个基准测试,比较使用ForClause的Go for语句和使用iter包的Go range语句。

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
$

你也可以退房 github.com/wushilin/stream < / p >

它是一个类似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()

希望这能有所帮助

虽然我同情你对缺乏这个语言特性的担忧,但你可能只是想使用一个普通的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)
}
}

然而,基准测试显示,使用通道是一个非常昂贵的选择。3个方法的比较,这可以从iter_test.go运行在我的包使用

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

在这个过程中,这个基准测试还显示了对于10大小的循环,bradfitz解决方案与内置的for子句相比表现不佳。

简而言之,到目前为止,似乎还没有找到一种方法来复制内置for子句的性能,同时为[0,n)提供一个简单的语法,就像在Python和Ruby中发现的那样。

这是一个遗憾,因为Go团队可能很容易向编译器添加一个简单的规则来更改一行,如

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

到与for i := 0; i < 10; i++相同的机器代码。

然而,公平地说,在编写了自己的iter.N之后(但在对其进行基准测试之前),我重新查看了最近编写的程序,以查看所有可以使用它的地方。实际上并不多。在我的代码的一个非重要部分中,只有一个地方可以不使用更完整的默认for子句。

因此,虽然从原则上看,这对语言来说是一个巨大的失望,但您可能会发现——就像我所做的那样——实际上在实践中并不真正需要它。就像Rob Pike所说的泛型一样,您可能不会像您想象的那样想念这个特性。

Mark Mishyn建议使用slice,但没有理由用make创建数组,并在for中使用它返回的slice,因为通过literal创建的数组可以使用,而且它更短

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


import "fmt"


func main() {


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

如果你只是想在一个w/o范围内使用和索引或其他东西进行迭代,这个代码样本对我来说很好。不需要额外的声明,不需要_。不过,还没有检查性能。

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

附注:在戈朗的第一天。如果这是一种错误的方法,请进行批评。

我用Golang写了一个包,它模仿了Python的range函数:

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
}


注意:我只是为了好玩才写的!顺便说一下,有时候可能会有帮助

下面是一个紧凑的动态版本,它不依赖iter(但工作方式类似):

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


经过一些调整,size可以是uint64类型(如果需要),但这是要点。

问题是的范围,问题是如何计算slice的结束。 对于固定数字10,简单的for循环是可以的,但是对于计算 size,如bfl.Size(),你在每次迭代中都得到一个函数调用。简单的range除以int32会有所帮助,因为这只计算bfl.Size()一次
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()
}