在 Python / Ruby / JavaScript / ECMAScript 6 中,可以使用yield语言提供的关键字编写生成器函数。在 Go 中,它可以使用一个 goroutine 和一个通道来模拟。
编码
以下代码显示了如何实现置换函数(abcd、abdc、acbd、acdb、...、dcba):
// $src/lib/lib.go
package lib
// private, starts with lowercase "p"
func permutateWithChannel(channel chan<- []string, strings, prefix []string) {
length := len(strings)
if length == 0 {
// Base case
channel <- prefix
return
}
// Recursive case
newStrings := make([]string, 0, length-1)
for i, s := range strings {
// Remove strings[i] and assign the result to newStringI
// Append strings[i] to newPrefixI
// Call the recursive case
newStringsI := append(newStrings, strings[:i]...)
newStringsI = append(newStringsI, strings[i+1:]...)
newPrefixI := append(prefix, s)
permutateWithChannel(channel, newStringsI, newPrefixI)
}
}
// public, starts with uppercase "P"
func PermutateWithChannel(strings []string) chan []string {
channel := make(chan []string)
prefix := make([]string, 0, len(strings))
go func() {
permutateWithChannel(channel, strings, prefix)
close(channel)
}()
return channel
}
以下是它的使用方法:
// $src/main.go
package main
import (
"./lib"
"fmt"
)
var (
fruits = []string{"apple", "banana", "cherry", "durian"}
banned = "durian"
)
func main() {
channel := lib.PermutateWithChannel(fruits)
for myFruits := range channel {
fmt.Println(myFruits)
if myFruits[0] == banned {
close(channel)
//break
}
}
}
笔记:
break不需要该语句(上面注释),因为close(channel)会导致在下一次迭代中range返回false,循环将终止。
问题
如果调用者不需要所有的排列,则需要close()显式地对通道进行处理,否则通道不会被关闭,直到程序终止(发生资源泄漏)。另一方面,如果调用者需要所有的排列(即range循环直到结束),调用者必须不是close()通道。这是因为close()-ing 一个已经关闭的通道会导致运行时恐慌(请参阅规范中的此处)。但是,如果判断是否应该停止的逻辑不像上图那么简单,我觉得还是使用defer close(channel).
实现这样的生成器的惯用方法是什么?
习惯上,谁应该close()
对通道负责——库函数还是调用者?
像下面这样修改我的代码是否是个好主意,以便调用者defer close()
无论如何都要对频道负责?
慕斯709654
慕丝7291255
守候你守候我
相关分类