go 实现迭代器
匿名函数实现
package main
import "fmt"
type Ints []int
func (i Ints) Iterator() func() (int, bool) {
index := 0
return func() (val int, ok bool) {
if index >= len(i) {
return
}
val, ok = i[index], true
index++
return
}
}
func main() {
ints := Ints{1, 2, 3}
next := ints.Iterator()
for {
val, ok := next()
if !ok {
break
}
fmt.Println(val)
}
}
Do实现
这份迭代器实现是最简洁的,代码也很直白,无须多言。如果想加上中断迭代的功能,可以将func(int)改为func(int)bool,Do中根据返回值决定是否退出迭代。
标准库中的container/ring中有Do()用法的例子。
package main
import "fmt"
type Ints []int
func (i Ints) Do(fn func(int)) {
for _, v := range i {
fn(v)
}
}
func main() {
ints := Ints{1, 2, 3}
ints.Do(func(v int) {
fmt.Println(v)
})
}
上篇写到C语言中的迭代器,今天写写Go语言中实现迭代器模式的方法。Go语言称为next C,在实现迭代器上,完全可以使用C语言中的思路,语法上更简练。但这里不使用这种经典的方式,而是使用Go语言本身带有的特性。我们会使用它的channel特性、goroutine特性和闭包特性。为了简单表达,这里的实现依旧使用双链表,如果要迭代更复杂的数据结构,比如树型结构,只要修改迭代器的算法实现过程即可。
使用channel特性实现迭代器
迭代器创建一个goroutine在后台迭代数据结构对象,把获得的Element发送到channel上,主线程从channel的另外一端取出Element,然后完成对Element的处理。从而,把可迭代对象(数据结构)的迭代和处理分离开来。channel本身在设计上就是线程安全,因此不用添加额外的锁。
package main
import (
"container/list"
"fmt"
)
type Iterator chan *list.Element
func NewIterator(list *list.List, buffer int) Iterator {
if buffer < 0 {
buffer = 0
}
iter := make(Iterator, buffer)
go func() {
for e := list.Front(); e != nil; e = e.Next() {
iter <- e
}
close(iter) // 关闭channel,避免一直阻塞goroutine而引起死锁
}()
return iter
}
func main() {
l := list.New()
l.PushBack("a")
l.PushBack("b")
l.PushBack("c")
l.PushBack("d")
l.PushBack("f")
for v := range NewIterator(list, 0) { // update
fmt.Println(v.Value.(string))
}
对于其他更复杂数据结构,迭代过程在go块内实现即可。出于安全考虑,我们还可以把NewIterator函数返回单向的channel。
上述的方法甚至可以更OOP,如下:
func (list *LocalList) NewIterator(buffer int) Iterator {
if buffer < 0 {
buffer = 0
}
iter := make(Iterator, buffer)
go func() {
for e := list.Front(); e != nil; e = e.Next() {
iter <- e
}
close(iter)
}()
return iter
}
使用闭包实现迭代器
闭包的特性就是携带函数作用域以外的变量,这些变量可以当做闭包的状态。这样,每次调用闭包(函数),修改外部状态,当调用完毕后,函数销毁,但外部状态还存在,下次调用闭包(函数)时,可以从该状态开始继续执行。利用这种原理,每次调用闭包迭代一次,把迭代的位置(即数据结构的位置)保存到外部状态中,下次从新迭代从外部状态中恢复。
package main
import (
"container/list"
"errors"
"fmt"
)
func NewClosureIterator(l *list.List) func() (value *list.Element, err error) {
var current *list.Element = l.Front()
var r *list.Element
fn := func() (value *list.Element, err error) {
r = current
if r == nil {
return nil, errors.New("StopIteration")
}
current = current.Next()
return r, nil
}
return fn
}
func main() {
l := list.New()
l.PushBack("a")
l.PushBack("b")
l.PushBack("c")
l.PushBack("d")
l.PushBack("f")
next := NewClosureIterator(l)
for {
v, err := next()
if err != nil {
break
}
fmt.Println(v.Value.(string))
}
}
通过上面的代码发现这种实现有点像装饰器模式,但不对,装饰器模式传入的是函数。对于更复杂的数据结构,迭代算法直接在闭包中实现即可。