有没有办法遍历整数范围?


174

Go的范围可以遍历地图和切片,但是我想知道是否有一种方法可以遍历一系列数字,例如:

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

还是有一种方法可以像GoRuby那样使用Range来表示Go中的整数范围

Answers:


224

您可以并且应该只编写一个for循环。简单,明显的代码就是Go的方式。

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

268
我认为大多数人都不会将这个三表达式版本称为@Vishnu所写的那样简单。仅在经过多年的C或Java灌输之后;-)
Thomas Ahle 2014年

12
IMO的要点是,您将始终拥有fo​​r循环的此三表达式版本(即,您可以使用它做更多的事情,OP的语法仅适用于数字范围更严格的情况,因此无论您使用哪种语言,都需要该扩展版本),它足以完成相同的任务,并且无论如何都没有明显的不同,所以为什么必须学习/记住另一种语法。如果您在一个大型而复杂的项目上进行编码,则不必担心编译器就各种语法(例如循环)而需要为编译器而战。
布拉德·皮博迪

3
@ThomasAhle特别考虑到C ++正式添加了受boost模板库启发的for_each(x,y)表示法
don bright

5
@BradPeabody这实际上是一个优先事项。Python没有3表达式循环,可以正常工作。许多人认为for-for语法不太容易出错,并且在本质上没有效率低下的问题。
VinGarcia

3
@necromancer是罗伯·派克(Rob Pike)的帖子,声称与我的回答大致相同。groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J。可能是Go社区不同意,但是当它与该语言的作者之一达成共识时,答案确实不是那么糟糕。
保罗·汉金

43

这是一个程序,可以比较到目前为止建议的两种方法

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
我看不到“迭代解决方案要昂贵得多”。您计算Go伪汇编程序指令的方法有缺陷。运行基准测试。
peterSO 2014年

11
一种解决方案可以调用runtime.makeslice,而另一种则不需要-我不需要基准就可以知道它会慢很多!
尼克·克雷格·伍德

6
runtime.makeslice如果您要求零大小分配,则“ 是”足够聪明而不分配任何内存。但是上面仍然称呼它,并且根据您的基准,在我的机器上确实需要10ns的时间。
尼克·克雷格·伍德

4
这使人想起出于性能原因建议在C ++上使用C
necromancer

5
在Goland中,辩论纳秒级CPU操作的运行时性能对我来说似乎很愚蠢。在可读性之后,我认为这是一个非常遥远的最后考虑。即使CPU性能相关,for循环的内容几乎总是淹没循环本身引起的任何差异。
乔纳森·哈特利

34

Mark Mishyn建议使用slice,但是当可以使用通过文字创建的数组且它更短时,没有理由make使用for它来创建数组并在返回的slice中使用

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

8
如果您不打算使用该变量,则也可以省略左侧并使用for range [5]int{} {
blockloop '18

6
缺点是5这里是文字,不能在运行时确定。
史蒂夫·鲍威尔

是更快还是可以与正常的三个循环表达式相比?
阿米特·特里帕蒂

@AmitTripathi是的,它具有可比性,数十亿次迭代的执行时间几乎相同。
丹尼尔·格兰金

18

iter是一个非常小的程序包,它仅提供了一种语法上不同的方式来迭代整数。

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

Go的作者Rob Pike 批评了它

似乎几乎每当有人想出一种方法来避免像惯用的for循环之类的事情时,因为感觉太长或太麻烦,结果几乎总是比按预期的更短的击键次数。[...]撇开了这些“改进”带来的所有疯狂开销。


16
派克的批评很简单,因为它只解决击键问题,而不是不断重新声明范围的精神负担。此外,与最现代的编辑器,该iter版本实际上使用较少的按键,因为rangeiter会自动完成。
克里斯·雷德福德

1
@ lang2,for循环不是Unix的头等公民。此外,不同于forseq流到标准输出一个数字序列。是否迭代它们取决于消费者。尽管for i in $(seq 1 10); do ... done 在Shell中很常见,但这只是执行for循环的一种方法seq,尽管它是非常常见的一种方法,但它本身也只是消耗的输出的一种方法。
丹尼尔·法瑞尔

2
而且,Pike根本不考虑可以以i in range(10)完全像对待的方式来构建编译器(鉴于语言规范包括此用例的范围语法)i := 0; i < 10; i++
鲁文·B.19年

8

这是比较Go for语句与ForClause和range使用该iter包的Go 语句的基准。

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,因此调用分配器(即使它足够聪明,不分配任何东西)仍然花费10 ns,并且还有一堆额外的I-cache破坏代码。
尼克·克雷格·伍德

我很想看到您对我创建并在答案中引用的程序包的基准和批评。
克里斯·雷德福德

5

尽管我对缺少此语言功能感到担忧,但您可能只想使用普通for循环即可。当您编写更多Go代码时,您可能会比想象中的还好。

我编写了这个iter程序包 -由一个简单的惯用for循环返回,该循环返回a上的值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在我的程序包中使用来运行

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

简而言之,到目前为止似乎还没有发现任何方法可以复制内置for子句的性能,同时为[0,n)类似于Python和Ruby中所。

太可惜了,因为Go团队可能很容易向编译器添加一条简单的规则来更改一行

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

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

但是,公平地讲,在编写了自己的游戏iter.N(但未进行基准测试)之后,我又回到了最近编写的程序,以查看可以使用它的所有位置。实际上没有很多。在我的代码的非重要部分中,只有一个地方可以没有更完整的默认for子句。

因此,尽管原则上看起来这对语言是一个巨大的失望,但您可能会像我一样发现,实际上您实际上并不需要它。就像Rob Pike所说的泛型一样,您实际上可能不会像您想的那样错过此功能。


1
使用通道进行迭代非常昂贵;goroutine和频道很便宜,它们不是免费的。如果通道上的迭代范围提前终止,则goroutine永远不会结束(goroutine泄漏)。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
在代码中添加一些上下文,以帮助将来的读者更好地理解其含义。
格兰特·米勒

3
这是什么?未定义总和。
naftalimich

0

我在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
}

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.