一种选择是使用渠道.通道在某种程度上类似于迭代器,您可以使用range关键字迭代它们.但是当你发现你不能在没有泄漏goroutine的情况下突破这个循环时,使用会变得有限.
在go中创建迭代器模式的惯用方法是什么?
编辑:
渠道的根本问题在于它们是推模型.迭代器是拉模型.你不必告诉iterator停止.我正在寻找一种以一种很好的表达方式迭代集合的方法.我还想链接迭代器(map,filter,fold alternative).
Son*_*nia 49
通道很有用,但闭包通常更合适.
package main
import "fmt"
func main() {
gen := newEven()
fmt.Println(gen())
fmt.Println(gen())
fmt.Println(gen())
gen = nil // release for garbage collection
}
func newEven() func() int {
n := 0
// closure captures variable n
return func() int {
n += 2
return n
}
}
Run Code Online (Sandbox Code Playgroud)
游乐场:http://play.golang.org/p/W7pG_HUOzw
不喜欢闭嘴吗?使用带有方法的命名类型:
package main
import "fmt"
func main() {
gen := even(0)
fmt.Println(gen.next())
fmt.Println(gen.next())
fmt.Println(gen.next())
}
type even int
func (e *even) next() int {
*e += 2
return int(*e)
}
Run Code Online (Sandbox Code Playgroud)
游乐场:http://play.golang.org/p/o0lerLcAh3
这三种技术之间存在权衡,因此您无法提名一种惯用语.使用最符合您需求的东西.
链接很容易,因为函数是一流的对象.这是闭包示例的扩展.我为整数生成器添加了一个类型intGen,它清楚地说明了生成器函数用作参数和返回值的位置.mapInt以一般方式定义,将任何整数函数映射到整数生成器.其他功能,如过滤器和折叠,可以类似地定义.
package main
import "fmt"
func main() {
gen := mapInt(newEven(), square)
fmt.Println(gen())
fmt.Println(gen())
fmt.Println(gen())
gen = nil // release for garbage collection
}
type intGen func() int
func newEven() intGen {
n := 0
return func() int {
n += 2
return n
}
}
func mapInt(g intGen, f func(int) int) intGen {
return func() int {
return f(g())
}
}
func square(i int) int {
return i * i
}
Run Code Online (Sandbox Code Playgroud)
游乐场:http://play.golang.org/p/L1OFm6JuX0
wld*_*svc 16
TL; DR:忘记关闭和通道,太慢了.如果可以通过索引访问集合的各个元素,那么请在类似数组的类型上进行经典的C迭代.如果没有,请实现有状态迭代器.
我需要迭代一些集合类型,其中确切的存储实现尚未确定.这个,以及从客户端抽象实现细节的其他原因,让我用各种迭代方法做一些测试.这里是完整代码,包括一些将错误用作值的实现.以下是基准测试结果:
类似数组结构的经典C迭代.该类型提供了ValueAt()和Len()方法:
l := Len(collection)
for i := 0; i < l; i++ { value := collection.ValueAt(i) }
// benchmark result: 2492641 ns/op
Run Code Online (Sandbox Code Playgroud)关闭样式迭代器.集合的Iterator方法返回next()函数(对集合和游标的闭包)和hasNext布尔值.next()返回下一个值和一个hasNext布尔值.请注意,这比使用单独的next()和hasNext()闭包返回单个值要快得多:
for next, hasNext := collection.Iterator(); hasNext; {
value, hasNext = next()
}
// benchmark result: 7966233 ns/op !!!
Run Code Online (Sandbox Code Playgroud)有状态迭代器.一个带有两个数据字段的简单结构,集合和游标,以及两个方法:Next()和HasNext().这次集合的Iterator()方法返回一个指向正确初始化的迭代器结构的指针:
for iter := collection.Iterator(); iter.HasNext(); {
value := iter.Next()
}
// benchmark result: 4010607 ns/op
Run Code Online (Sandbox Code Playgroud)尽管我喜欢关闭,性能明智但它是一个禁忌.至于设计模式,Gophers更喜欢"惯用的方式"这个词,这是有充分理由的.另外grep the it source tree for iterators:如此少的文件提到名称,迭代器肯定不是Go的东西.
另请查看此页面:http://ewencp.org/blog/golang-iterators/
无论如何,接口在这里没有任何帮助,除非你想定义一些Iterable接口,但这是一个完全不同的主题.
Son*_*nia 14
TL; DR:迭代器在Go中不是惯用的.将它们留给其他语言.
然后,维基百科条目"Iterator模式"开始,"在面向对象的编程中,迭代器模式是一种设计模式......"两个红旗:首先,面向对象的编程概念通常不能很好地转化为Go第二,许多Go程序员对设计模式没有太多考虑.第一段还包括"迭代器模式从容器中解耦算法",但只是在声明"迭代器[访问]容器的元素之后.那么它是什么?如果算法正在访问容器的元素,它几乎不能声称是解耦的.许多语言的答案都涉及某种泛型,它允许语言概括为类似的数据结构.Go中的答案是接口.接口通过拒绝访问结构并要求所有交互都基于行为来强制执行更严格的算法和对象解耦行为意味着通过数据方法表达的能力.
对于最小迭代器类型,所需的功能是Next方法.Go接口可以通过简单地指定此单个方法签名来表示迭代器对象.如果希望容器类型可迭代,则必须通过实现接口的所有方法来满足迭代器接口.(我们这里只有一个,实际上接口通常只有一个方法.)
最小的工作示例:
package main
import "fmt"
// IntIterator is an iterator object.
// yes, it's just an interface.
type intIterator interface {
Next() (value int, ok bool)
}
// IterableSlice is a container data structure
// that supports iteration.
// That is, it satisfies intIterator.
type iterableSlice struct {
x int
s []int
}
// iterableSlice.Next implements intIterator.Next,
// satisfying the interface.
func (s *iterableSlice) Next() (value int, ok bool) {
s.x++
if s.x >= len(s.s) {
return 0, false
}
return s.s[s.x], true
}
// newSlice is a constructor that constructs an iterable
// container object from the native Go slice type.
func newSlice(s []int) *iterableSlice {
return &iterableSlice{-1, s}
}
func main() {
// Ds is just intIterator type.
// It has no access to any data structure.
var ds intIterator
// Construct. Assign the concrete result from newSlice
// to the interface ds. ds has a non-nil value now,
// but still has no access to the structure of the
// concrete type.
ds = newSlice([]int{3, 1, 4})
// iterate
for {
// Use behavior only. Next returns values
// but without insight as to how the values
// might have been represented or might have
// been computed.
v, ok := ds.Next()
if !ok {
break
}
fmt.Println(v)
}
}
Run Code Online (Sandbox Code Playgroud)
游乐场:http://play.golang.org/p/AFZzA7PRDR
这是接口的基本思想,但迭代切片是荒谬的过度杀伤.在很多情况下,你会达到在其他语言中的迭代器,你写Go代码使用内置的直接遍历基本类型的语言原语.您的代码保持清晰简洁.如果变得复杂,请考虑您真正需要的功能.你需要在某些函数中从随机位置发出结果吗?通道提供类似产量的功能,允许这样做.您需要无限列表或懒惰评估吗?闭包效果很好.您是否有不同的数据类型,并且需要它们透明地支持相同的操作?接口提供.通道,函数和接口都是一流的对象,这些技术都很容易组合.那么最惯用的方式是什么呢?它是尝试不同的技术,熟悉它们,并以最简单的方式使用满足您需求的任何东西.无论如何,面向对象意义上的迭代器几乎从不是最简单的.
| 归档时间: |
|
| 查看次数: |
18652 次 |
| 最近记录: |