Go的range可以迭代地遍历maps和slices,但我想知道是否有一种方法可以迭代数值范围,就像这样:
for i := range [1..10] {
fmt.Println(i)
}
在Go语言中是否有一种像Ruby的Range类那样表示整数范围的方法?
在 Go 中惯用的方法是编写类似这样的 for 循环。
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
使用范围确实有优势,并且它们在许多其他语言中得到了使用,但Go的设计原则是仅在好处显著超过成本(包括使语言更大的成本)的情况下引入抽象。对于范围的成本和收益,理智的人会有不同的看法,但这个答案是我试图描述Go惯用语的方式。
i
的值会导致它自我销毁,而使用range方法则不会受到影响。此外,“DRY”是一种被广泛接受的语言设计原则,在Rob的循环中,你可以看到i
出现了3次!无论如何,现在看着我在重复自己。我将把第一个评论中的每个赞视为礼貌的反对票。 - necromancerMark Mishyn建议使用slice,但是没有必要使用make
创建数组并在for
中使用它返回的切片,因为可以使用通过文字创建的数组,并且更短
for i := range [5]int{} {
fmt.Println(i)
}
for range [5]int{} {
。 - blockloop5
是一个字面值,无法在运行时确定。 - Steve PowellO(n)
的内存吗? - Ian这里有一个比较目前为止提出的两种方法的程序
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循环。
runtime.makeslice
,而另一个则没有 - 我不需要进行基准测试就知道它会慢得多! - Nick Craig-Woodruntime.makeslice
非常聪明,如果您要求分配零大小的内存,它不会分配任何内存。但是上面的代码仍在调用它,并且根据您的基准测试,在我的机器上需要比原来多花费 10 纳秒的时间。 - Nick Craig-Wooditer
版本实际上需要的按键次数更少,因为range
和iter
会自动完成。 - Chris Redfordi in range(10)
正好处理成类似于 i := 0; i < 10; i++
的形式。请注意,本文只是翻译,不包括任何解释或其他内容。 - Robsdedudeiter_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
$
_
。尽管我没有检查过性能。for range [N]int{} {
// Body...
}
P.S. 第一次学习Go语言。如果这是错误的方法,请批评指正。
for
循环。随着你编写更多 Go 代码,你可能会发现这样做比你想象中更好。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)
}
}
然而,基准测试表明使用通道是一种非常昂贵的选择。可以从我的包中的iter_test.go
运行的3种方法进行比较。
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
语句的性能,同时提供类似于Python和Ruby中所发现的[0,n)
的简单语法。for i := range 10 {
fmt.Println(i)
}
该代码将被编译为与for i := 0; i < 10; i++
相同的机器码。
然而,公平地说,在编写自己的 iter.N
(但在对其进行基准测试之前),我回顾了最近编写的程序,看看所有可以使用它的地方。实际上并不多。只有一个位置,在我的代码中非关键部分,我可以不使用更完整的默认for
子句。
因此,虽然从原则上看,这可能看起来是语言上的巨大失望,但您可能会发现 - 就像我一样 - 在实践中您实际上并不真正需要它。就像 Rob Pike 对泛型所说的那样,您可能并不会像您认为的那样非常想念这个功能。
// 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()
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
类型(如果需要的话),但这就是要点。