【问题标题】:Is there a way to iterate over a range of integers?有没有办法迭代一系列整数?
【发布时间】:2014-03-23 21:37:32
【问题描述】:

Go 的范围可以迭代地图和切片,但我想知道是否有一种方法可以迭代一系列数字,如下所示:

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

或者有没有办法在 Go 中表示整数范围,就像 Ruby 对 class Range 所做的那样?

【问题讨论】:

标签: loops go integer range


【解决方案1】:

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

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

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

【讨论】:

  • 我不认为大多数人会称这个三表达式版本比@Vishnu 写的更简单。可能只是经过多年的 C 或 Java 灌输 ;-)
  • IMO 的关键是你总是会有这个三表达式版本的 for 循环(即你可以用它做更多的事情,来自 OP 的语法只适用于更多数字范围的限制大小写,所以在任何语言中你都需要这个扩展版本)并且它足以完成相同的任务,并且无论如何都没有显着不同,所以为什么必须学习/记住另一种语法。如果您正在编写一个大型而复杂的项目,那么您已经有足够的担心了,而不必为像循环这样简单的事情与编译器争论各种语法。
  • @ThomasAhle 特别是考虑到 C++ 正式添加了受 boost 模板库启发的符号 for_each(x,y)
  • @BradPeabody 这实际上是一个偏好问题。 Python 没有 3 表达式循环并且工作正常。许多人认为 for-each 语法不太容易出错,并且它本质上没有什么低效的地方。
  • @PaulHankin 这完全取决于您将编码视为科学还是宗教。不幸的是,Rob 犯了错误,然后在这里进行了防守性的强力跳闸,而您正在盲目地引导他的手下。为了您自己的工程利益,不要忽视在 Rob 的循环中更改 i 的值会导致它自毁,而范围方法是免疫的。而且,“DRY”是一种普遍接受的语言设计原则,你可以在 Rob 的循环中看到 i 3 次!不管怎样,现在看我重复自己。我会将第一条评论的每个赞成票视为礼貌的反对票。
【解决方案2】:

问题是不是范围,问题是切片的end是如何计算的。 使用固定数字 10 简单的 for 循环是可以的,但是使用 calculated sizebfl.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()
  }

【讨论】:

    【解决方案3】:

    这是一个紧凑的动态版本,不依赖于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 类型(如果需要),但这是要点。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        如果您只想在不使用和索引或其他任何内容的情况下迭代范围,此代码示例对我来说效果很好。无需额外声明,无需_。不过还没有检查性能。

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

        附: GoLang 的第一天。如果是错误的方法,请批评。

        【讨论】:

        • 到目前为止(版本 1.13.6),它不起作用。向我扔non-constant array bound
        • N 必须是常数:for range [5]int{} {}
        【解决方案6】:

        您也可以查看 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()
        

        希望对你有帮助

        【讨论】:

          【解决方案7】:
          package main
          
          import "fmt"
          
          func main() {
          
              nums := []int{2, 3, 4}
              for _, num := range nums {
                 fmt.Println(num, sum)    
              }
          }
          

          【讨论】:

          • 为您的代码添加一些上下文,以帮助未来的读者更好地理解其含义。
          • 这是什么?总和未定义。
          【解决方案8】:

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

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

          【讨论】:

          • 如果您不打算使用该变量,您也可以省略左侧并使用for range [5]int{} {
          • 缺点是这里的5是一个字面量,不能在运行时确定。
          • 它是否比普通的三个循环表达式更快或可比?
          • @AmitTripathi 是的,它具有可比性,数十亿次迭代的执行时间几乎相同。
          【解决方案9】:

          虽然我对您对缺少此语言功能的担忧表示同情,但您可能只想使用普通的 for 循环。随着你编写更多的 Go 代码,你可能会比你想象的更好。

          我写了this iter package——它由一个简单的、惯用的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 循环大小的内置 for 子句相比,bradfitz 解决方案的性能如何。

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

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

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

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

          但是,公平地说,在编写了自己的 iter.N 之后(但在对其进行基准测试之前),我回顾了一个最近编写的程序来查看我可以使用它的所有地方。其实没有多少。只有一个地方,在我的代码的非重要部分,我可以在没有更完整的默认 for 子句的情况下度过难关。

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

          【讨论】:

          【解决方案10】:

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

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

          Rob Pike(围棋作者)has criticized it

          似乎几乎每次有人想出一种方法来避免 以惯用的方式做类似 for 循环的事情,因为它感觉 太长或太麻烦,结果几乎总是更多的击键 比据说更短的东西。 [...] 撇开这些“改进”带来的所有疯狂开销。

          【讨论】:

          • Pike 的批评过于简单,因为它只解决了击键问题,而不是不断重新声明范围的心理开销。此外,对于大多数现代编辑器,iter 版本实际上使用更少的击键,因为rangeiter 将自动完成。
          • @lang2, for 循环不像在 go 中那样是 Unix 的一等公民。此外,与for 不同,seq 流向标准输出一个数字序列。是否对它们进行迭代取决于消费者。虽然for i in $(seq 1 10); do ... done 在Shell 中很常见,但它只是执行for 循环的一种方式,而for 循环本身只是消耗seq 输出的一种方式,尽管这是一种非常常见的方式。
          • 另外,Pike 根本没有考虑编译(鉴于语言规范包括此用例的范围语法)可以构建为将 i in range(10) 完全视为 @ 987654333@.
          • 仅供参考:“这个包 ['iter' 包] 在 2014 年发布时旨在成为一个教育笑话。人们没有得到笑话部分并开始依赖它。没关系,我猜。 (这是互联网。)但这有点奇怪。这是一行,甚至不是惯用的 Go 风格。”
          【解决方案11】:

          这是一个比较 Go for 语句与 ForClause 和使用 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
          $
          

          【讨论】:

          • 如果您将循环设置为 10,然后重试基准测试,您将看到明显的差异。在我的机器上,ForClause 需要 5.6 ns,而 Iter 需要 15.4 ns,所以调用分配器(即使它足够聪明,不会分配任何东西)仍然需要 10ns 和一大堆额外的 I-cache 破坏代码。
          • 我很想看看你对 I created and referenced in my answer 包的基准和批评。
          【解决方案12】:

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

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

          【讨论】:

          • 我无法“看到 iter 解决方案的成本要高得多。”您计算 Go 伪汇编指令的方法有缺陷。运行基准测试。
          • 一个解决方案调用 runtime.makeslice 而另一个没有 - 我不需要基准就知道它会慢很多!
          • 是的runtime.makeslice 足够聪明,如果您要求零大小分配,它不会分配任何内存。但是上面仍然调用它,并且根据你的基准在我的机器上确实需要 10nS 的时间。
          • 这提醒人们出于性能原因建议使用 C 而不是 C++
          • 讨论纳秒 CPU 操作的运行时性能,虽然在 Goland 很常见,但对我来说似乎很愚蠢。在可读性之后,我认为这是一个非常遥远的最后考虑。即使 CPU 性能是相关的,for 循环的内容几乎总是会淹没循环本身产生的任何差异。
          猜你喜欢
          • 2019-11-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-23
          • 2020-02-24
          • 2019-09-22
          • 2021-08-25
          • 1970-01-01
          相关资源
          最近更新 更多