什么是在Go语言中创建迭代器的最常用方式?

huangapple go评论157阅读模式
英文:

What is most idiomatic way to create an iterator in Go?

问题

一种选择是使用通道。通道在某种程度上类似于迭代器,可以使用range关键字对其进行迭代。但是当你发现在不泄漏goroutine的情况下无法跳出循环时,使用通道的限制就变得很大。

在Go语言中,创建迭代器模式的惯用方式是什么?

编辑

通道的根本问题在于它们是一种推模型。而迭代器是一种拉模型。你不需要告诉迭代器停止。我正在寻找一种以简洁表达方式遍历集合的方法。我还希望能够链式使用迭代器(map、filter、fold等替代方案)。

英文:

One option is to use channels. Channels are like iterators in a way and you can iterate over them using range keyword. But when you find out you can't break out of this loop without leaking goroutine the usage becomes limited.

What is the idiomatic way to create iterator pattern in go?

Edit:

The fundamental problem with channels is they are a push model. Iterator is is a pull model. You don't have to tell iterator to stop. I'm looking for a way to iterate over collections in a nice expressive way. I would also like to chain iterators (map, filter, fold alternatives).

答案1

得分: 80

通道很有用,但闭包通常更合适。

package main

import "fmt"

func main() {
    gen := newEven()
    fmt.Println(gen())
    fmt.Println(gen())
    fmt.Println(gen())
    gen = nil // 释放以进行垃圾回收
}

func newEven() func() int {
    n := 0
    // 闭包捕获变量n
    return func() int {
        n += 2
        return n
    }
}

Playground: 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)
}

Playground: http://play.golang.org/p/o0lerLcAh3

这三种技术之间存在权衡,因此不能将其中一种指定为惯用法。使用最适合您需求的方法。

由于函数是一等对象,所以链接很容易。这是闭包示例的扩展。我添加了一个名为intGen的整数生成器类型,这样就清楚了生成器函数在哪里用作参数和返回值。mapInt以一种通用的方式定义,将任何整数函数映射到整数生成器。类似地,可以定义其他函数,如filter和fold。

package main

import "fmt"

func main() {
    gen := mapInt(newEven(), square)
    fmt.Println(gen())
    fmt.Println(gen())
    fmt.Println(gen())
    gen = nil // 释放以进行垃圾回收
}

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
}

Playground: http://play.golang.org/p/L1OFm6JuX0

英文:

Channels are useful, but closures are often more suitable.

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

Playground: http://play.golang.org/p/W7pG_HUOzw

Don't like closures either? Use a named type with a method:

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)
}

Playground: http://play.golang.org/p/o0lerLcAh3

There are tradeoffs among the three techniques so you can't nominate one as idiomatic. Use whatever best meets your needs.

Chaining is easy because functions are first class objects. Here's an extension of the closure example. I added a type intGen for integer generator which makes it clear where generator functions are used as arguments and return values. mapInt is defined in a general way to map any integer function to an integer generator. Other functions such as filter and fold could be defined similarly.

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
}

Playground: http://play.golang.org/p/L1OFm6JuX0

答案2

得分: 41

TL;DR: 忘记闭包和通道,太慢了。如果你的集合的各个元素可以通过索引访问,那就使用经典的C迭代数组类型。如果不能,就实现一个有状态的迭代器。

我需要迭代一些集合类型,但存储实现方式还没有确定。这个需求以及其他很多原因都导致我对各种迭代方法进行了一些测试。完整代码在这里,包括一些使用错误作为值的实现。以下是基准测试结果:

  • 经典的C迭代数组结构。该类型提供了ValueAt()和Len()方法:

      l := Len(collection)
      for i := 0; i < l; i++ { value := collection.ValueAt(i) }
      // 基准测试结果:2492641 ns/op
    
  • 闭包风格的迭代器。集合的Iterator方法返回一个next()函数(闭包,包含集合和游标)和一个hasNext布尔值。next()返回下一个值和一个hasNext布尔值。注意,这比使用单独的返回单个值的next()和hasNext()闭包运行得更快:

      for next, hasNext := collection.Iterator(); hasNext; {
          value, hasNext = next()
      }
      // 基准测试结果:7966233 ns/op !!!
    
  • 有状态的迭代器。一个简单的结构体,有两个数据字段,集合和游标,以及两个方法:Next()和HasNext()。这次,集合的Iterator()方法返回一个指向正确初始化的迭代器结构体的指针:

      for iter := collection.Iterator(); iter.HasNext(); {
          value := iter.Next()
      }
      // 基准测试结果:4010607 ns/op
    

尽管我喜欢闭包,但从性能上来说,不适合。至于设计模式,嗯,Gophers更喜欢用“惯用的方式”来做事情,有很好的理由。还可以在Go源代码树中搜索迭代器:只有很少的文件提到了这个名字,迭代器明显不是Go的东西。

还可以查看这个页面:http://ewencp.org/blog/golang-iterators/

无论如何,在这里接口没有任何帮助,除非你想定义一个Iterable接口,但这是一个完全不同的主题。

英文:

TL;DR: Forget closures and channels, too slow. If the individual elements of your collection are accessible by index, go for the classic C iteration over an array-like type. If not, implement a stateful iterator.

I needed to iterate over some collection type for which the exact storage implementation is not set in stone yet. This, plus the zillions other reasons to abstract the implementation details from the client, lead me to do some testing with various iteration methods. Full code here, including some implementations that make use of errors as values. Here are the benchmark results:

  • classic C iteration over an array-like structure. The type provides the methods ValueAt() and Len():

     l := Len(collection)
     for i := 0; i &lt; l; i++ { value := collection.ValueAt(i) }
     // benchmark result: 2492641 ns/op
    
  • Closure style iterator. The collection's Iterator method returns a next() function (a closure over the collection and cursor) and a hasNext boolean. next() returns the next value and a hasNext boolean. Note that this runs much faster than using separate next() and hasNext() closures returning single values:

     for next, hasNext := collection.Iterator(); hasNext; {
         value, hasNext = next()
     }
     // benchmark result: 7966233 ns/op !!!
    
  • Stateful iterator. A simple struct with two data fields, the collection and a cursor, and two methods: Next() and HasNext(). This time the Iterator() method of the collection returns a pointer to a properly initialized iterator structure:

     for iter := collection.Iterator(); iter.HasNext(); {
         value := iter.Next()
     }
     // benchmark result: 4010607 ns/op
    

As much as I like closures, performance wise it's a no-Go. As for design patterns, well, Gophers prefer the term "idiomatic way to do" stuff for good reason. Also grep the go source tree for iterators: with so few files that mention the name, iterators are definitely not a Go thing.

Also check out this page: http://ewencp.org/blog/golang-iterators/

Anyhow, interfaces do not help in any way here, unless you want to define some Iterable interface, but this is a completely different subject.

答案3

得分: 24

TL;DR: 迭代器在Go语言中不是惯用的。将它们留给其他语言。

深入来看,维基百科中的“迭代器模式”条目开始说,“在面向对象编程中,迭代器模式是一种设计模式...” 这里有两个警示信号:首先,面向对象编程的概念通常在Go语言中无法很好地转化,其次,许多Go程序员对设计模式并不看重。该段落还提到“迭代器模式将算法与容器解耦”,但是在之前已经声明“迭代器[访问]容器的元素”。那到底是哪种情况呢?如果算法正在访问容器的元素,它很难声称已经解耦。在许多语言中,答案涉及某种泛型,允许语言对类似的数据结构进行泛化。而在Go语言中,答案是接口。接口通过拒绝访问结构并要求所有交互都基于行为来更严格地解耦算法和对象。行为意味着通过数据上的方法来表达能力。

对于最小的迭代器类型,所需的能力是Next方法。通过仅指定这个单一方法签名,Go接口可以表示迭代器对象。如果要使容器类型可迭代,它必须通过实现接口的所有方法来满足迭代器接口。 (我们这里只有一个方法,实际上接口通常只有一个方法。)

一个最小的工作示例:

package main

import "fmt"

// IntIterator是一个迭代器对象。
// 是的,它只是一个接口。
type intIterator interface {
    Next() (value int, ok bool)
}

// IterableSlice是一个支持迭代的容器数据结构。
// 也就是说,它满足intIterator。
type iterableSlice struct {
    x int
    s []int
}

// iterableSlice.Next实现了intIterator.Next,
// 满足了接口。
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是一个从原生Go切片类型构造可迭代容器对象的构造函数。
func newSlice(s []int) *iterableSlice {
    return &iterableSlice{-1, s}
}

func main() {
    // Ds只是intIterator类型。
    // 它无法访问任何数据结构。
    var ds intIterator

    // 构造。将newSlice的具体结果分配给接口ds。
    // ds现在具有非nil值,但仍无法访问具体类型的结构。
    ds = newSlice([]int{3, 1, 4})

    // 迭代
    for {
        // 仅使用行为。Next返回值,
        // 但无法了解这些值可能是如何表示或计算的。
        v, ok := ds.Next()
        if !ok {
            break
        }
        fmt.Println(v)
    }
}

Playground: http://play.golang.org/p/AFZzA7PRDR

这是接口的基本思想,但对于迭代切片来说,这是过度的。在许多情况下,你在其他语言中会使用迭代器,而在Go语言中,你可以使用内置的语言原语直接迭代基本类型。你的代码保持清晰简洁。当情况变得复杂时,请考虑你真正需要的功能。你是否需要从某个函数的任意位置发出结果?通道提供了类似yield的功能。你是否需要无限列表或惰性求值?闭包非常适用。你是否有不同的数据类型,并且需要它们透明地支持相同的操作?接口可以实现。通过通道、函数和接口都是一等对象,这些技术都可以轻松组合。那么什么是最惯用的方式呢?它是尝试不同的技术,熟悉它们,并以最简单的方式满足你的需求。迭代器,在面向对象的意义上,几乎从来都不是最简单的方式。

英文:

TL;DR: Iterators are not idiomatic in Go. Leave them to other languages.

In depth then, the Wikipedia entry "Iterator pattern" begins, "In object-oriented programming, the iterator pattern is a design pattern..." Two red flags there: First, object oriented programming concepts often don't translate well into Go, and second, many Go programmers don't think much of design patterns. That first paragraph also includes "The iterator pattern decouples algorithms from containers", but only after stating "an iterator [accesses] the container's elements. Well which is it? If an algorithm is accessing the container's elements it can hardly claim to be decoupled. The answer in many languages involves some kind of generics that allows the language to generalize over similar data structures. The answer in Go is interfaces. Interfaces enforce a stricter decoupling of algorithms and objects by denying access to structure and requiring all interaction be based on behavior. Behavior means capabilites expressed through methods on the data.

For a minimal iterator type the needed capability is a Next method. A Go interface can represent an iterator object by simply specifying this single method signature. If you want a container type to be iterable, it must satisfy the iterator interface by implementing all of the methods of the interface. (We only have one here, and in fact it is common for interfaces to have only a single method.)

A minimal working example:

package main

import &quot;fmt&quot;

// IntIterator is an iterator object.
// yes, it&#39;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 &gt;= 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 &amp;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)
	}
}

Playground: http://play.golang.org/p/AFZzA7PRDR

This is the basic idea of interfaces, but it's absurd overkill for iterating over a slice. In many cases where you would reach for an iterator in other languages, you write Go code using built in language primitives that iterate directly over basic types. Your code stays clear and concise. Where that gets complicated, consider what features you really need. Do you need to emit results from random places in some function? Channels provide a yield-like capability that allows that. Do you need infinite lists or lazy evaluation? Closures work great. Do you have different data types and you need them to transparently support the same operations? Interfaces deliver. With channels, functions, and interfaces all first class objects, these techniques are all easily composable. So what then is the most idiomatic way? It is to experiment with different techniques, get comfortable with them, and use whatever meets your needs in the simplest way possible. Iterators, in the object oriented sense anyway, are almost never simplest.

答案4

得分: 4

你可以通过给你的goroutine提供一个第二个用于控制消息的通道来避免泄漏。在最简单的情况下,它只是一个chan bool。当你想要停止goroutine时,你在这个通道上发送消息。在goroutine内部,你将迭代器的通道发送和监听控制通道的操作放在一个select语句中。

这里有一个例子。

你可以进一步扩展这个功能,允许不同的控制消息,比如“跳过”。

你的问题比较抽象,所以为了更好地回答,一个具体的例子会很有帮助。

英文:

You can break out without leaking by giving your goroutines a second channel for control messages. In the simplest case it's just a chan bool. When you want the goroutine to stop, you send on this channel. Inside the goroutine, you put the iterator's channel send and the listen on the control channel inside a select.

Here's an example.

You can take this further by allowing different control messages, such as "skip".

Your question is pretty abstract, so to say more, a concrete example would be helpful.

答案5

得分: 4

这是我想到的一种使用通道和goroutine的方法:

package main

import (
    "fmt"
)

func main() {
    c := nameIterator(3)
    for batch := range c {
        fmt.Println(batch)
    }
}

func nameIterator(batchSize int) <-chan []string {
    names := []string{"Cherry", "Cami", "Tildy", "Cory", "Ronnie", "Aleksandr", "Billie", "Reine", "Gilbertina", "Dotti"}

    c := make(chan []string)

    go func() {
        defer close(c)
        for i := 0; i < len(names); i++ {
            startIdx := i * batchSize
            endIdx := startIdx + batchSize

            if startIdx > len(names) {
                continue
            }
            if endIdx > len(names) {
                c <- names[startIdx:]
            } else {
                c <- names[startIdx:endIdx]
            }
        }    
    }()

    return c
}

https://play.golang.org/p/M6NPT-hYPNd

我从Rob Pike的Go并发模式演讲中得到了这个想法。

英文:

Here's a way I thought of to do it with channels and goroutines:

package main

import (
	&quot;fmt&quot;
)

func main() {
	c := nameIterator(3)
	for batch := range c {
		fmt.Println(batch)
	}
}

func nameIterator(batchSize int) &lt;-chan []string {
	names := []string{&quot;Cherry&quot;, &quot;Cami&quot;, &quot;Tildy&quot;, &quot;Cory&quot;, &quot;Ronnie&quot;, &quot;Aleksandr&quot;, &quot;Billie&quot;, &quot;Reine&quot;, &quot;Gilbertina&quot;, &quot;Dotti&quot;}

	c := make(chan []string)

	go func() {
		defer close(c)
		for i := 0; i &lt; len(names); i++ {
			startIdx := i * batchSize
			endIdx := startIdx + batchSize

			if startIdx &gt; len(names) {
				continue
			}
			if endIdx &gt; len(names) {
				c &lt;- names[startIdx:]
			} else {
				c &lt;- names[startIdx:endIdx]
			}
		}    
	}()

	return c
}

https://play.golang.org/p/M6NPT-hYPNd

I got the idea from Rob Pike's Go Concurrency Patterns talk.

答案6

得分: 3

查看container/list包,似乎没有办法做到这一点。如果您要迭代对象,应该使用类似C的方式。

类似这样的代码。

type Foo struct {
...
}

func (f *Foo) Next() int {
...
}

foo := Foo(10)

for f := foo.Next(); f >= 0; f = foo.Next() {
...
}
英文:

Looking to the container/list package it looks like there is no way to do it. C-like way should be used if you iterate over object.

Something like this.

type Foo struct {
...
}

func (f *Foo) Next() int {
...
}

foo := Foo(10)

for f := foo.Next(); f &gt;= 0; f = foo.Next() {
...
}

答案7

得分: 3

我在这个主题上发表了一篇文章:

https://serge-hulne.medium.com/iterators-map-filter-reduce-and-list-processing-in-go-golang-implementing-python-functional-2d24d780051f

有一个相关的Git仓库:https://github.com/serge-hulne/iter/tree/main/iterate

主要思想是:

func Fib(n int) chan int {
	out := make(chan int)
	go func() {
		defer close(out)
		for i, j := 0, 1; i < n; i, j = i+j, i {
			out <- i
		}
	}()
	return out
}

用法如下:

fibs = Fib(100)
for i := range Map(fibs) {
	fmt.Printf("i = %6v\n", i)
}
英文:

I published an article on this subject :

https://serge-hulne.medium.com/iterators-map-filter-reduce-and-list-processing-in-go-golang-implementing-python-functional-2d24d780051f

There's an
associated Git repo: https://github.com/serge-hulne/iter/tree/main/iterate

The main idea is :

func Fib(n int) chan int {
	out := make(chan int)
	go func() {
		defer close(out)
		for i, j := 0, 1; i &lt; n; i, j = i+j, i {
			out &lt;- i
		}
	}()
	return out
}

Used as:

fibs = Fib(100)
for i := range Map(fibs) {
	fmt.Printf(&quot;i = %6v\n&quot;, i)
}

答案8

得分: 1

这里有很多看似不同的解决方案,这意味着似乎没有一种惯用的方法来做到这一点。我正在学习Go,并且我认为应该有一种方法来利用range的强大功能。可惜没有。

这是我想出来的解决方案(与上面的一些解决方案类似):

// Node 基本上,这是迭代器(或其头部)和可迭代类型的脚手架
type Node struct {
	next *Node
}

func (node *Node) Next() (*Node, bool) {
	return node.next, node.next != nil
}

// Add 添加下一个节点
func (node *Node) Add(another *Node) {
	node.next = another
}

这是我如何使用它的方式:

node := &Node{}
node.Add(&Node{})

for goOn := true; goOn; node, goOn = node.Next() {
	fmt.Println(node)
}

或者可能更优雅的解决方案:

...
func (node *Node) Next() *Node {
	return node.next
}
...

for ; node != nil; node = node.Next() {
	fmt.Println(node)
}
英文:

The very fact that there are so many seemingly different solutions here means that the doesn't seem to be an idiomatic way of doing it. I am starting my way in Go, and I thought there would be a way to tap into the power of range thing. Sadly, no.

Here's what I came up with (it is similar to some of the solutions above)

// Node Basically, this is the iterator (or the head of it) 
// and the scaffolding for your itterable type
type Node struct {
	next *Node
}

func (node *Node) Next() (*Node, bool) {
	return node.next, node.next != nil
}

// Add add the next node
func (node *Node) Add(another *Node) {
	node.next = another
}

and here is how I use it:

node := &amp;Node{}
node.Add(&amp;Node{})

for goOn := true; goOn; node, goOn = node.Next() {
	fmt.Println(node)
}

Or probably a more elegant solution:

...
func (node *Node) Next() *Node {
	return node.next
}
...

for ; node != nil; node = node.Next() {
	fmt.Println(node)
}

答案9

得分: 0

如其他同事所说,你可以使用通道来实现生成器设计模式,这正是你要寻找的。

生成器函数

通道和goroutine为使用生成器函数实现生产者/消费者模式提供了自然的基础。在这种方法中,一个goroutine被包装在一个函数中,该函数生成通过函数返回的通道发送的值。消费者goroutine接收这些生成的值。

Go设计模式应用于实际中提取的示例:

package main

import (
	"fmt"
	"strings"
)

func main() {
	data := []string{"Sphinx of black quartz, judge my vow",
		"The sky is blue and the water too",
		"Cozy lummox gives smart squid who asks for job pen",
		"Jackdaws love my big sphinx of quartz",
		"The quick onyx goblin jumps over the lazy dwarf"}
	histogram := make(map[string]int)
	words := words(data) // 返回数据通道的句柄
	for word := range words { // 每次从通道中读取一个单词
		histogram[word]++
	}
	fmt.Println(histogram)
}

// 生成数据的生成器函数
func words(data []string) <-chan string {
	out := make(chan string)
	// Go协程
	go func() {
		defer close(out) // 函数返回时关闭通道
		for _, line := range data {
			words := strings.Split(line, " ")
			for _, word := range words {
				word = strings.ToLower(word)
				out <- word // 将单词发送到通道
			}
		}
	}()
	return out
}

https://play.golang.org/p/f0nynFWbEam

在这个示例中,生成器函数声明为func words(data []string) <-chan string,返回一个只接收字符串元素的通道。消费者函数,在这个例子中是main(),接收生成器函数发出的数据,并使用for…range循环进行处理。

这个设计模式的改进版本:

https://play.golang.org/p/uyUfz3ALO6J

添加了NextError方法:

type iterator struct {
	valueChan <-chan interface{}
	okChan    <-chan bool
	errChan   <-chan error
	err       error
}

func (i *iterator) next() (interface{}, bool) {
	var (
		value interface{}
		ok    bool
	)
	value, ok, i.err = <-i.valueChan, <-i.okChan, <-i.errChan
	return value, ok
}

func (i *iterator) error() error {
	return i.err
}

// 生成数据的生成器函数
func NewIterator(data []string) iterator {
	out := make(chan interface{})
	ok := make(chan bool)
	err := make(chan error)
	// Go协程
	go func() {
		defer close(out) // 函数返回时关闭通道
		for _, line := range data {
			words := strings.Split(line, " ")
			for _, word := range words {
				word = strings.ToLower(word)
				out <- word // 将单词发送到通道并等待其读取
				ok <- true
				err <- nil // 如果有任何错误,改变其值
			}
		}
		out <- ""
		ok <- false
		err <- nil
	}()

	return iterator{out, ok, err, nil}
}
英文:

As other mates said you can work with channels to implement Generator design pattern which is what you're looking for.

Generator functions

Channels and goroutines provide a natural substrate for implementing a form of
producer/producer pattern using generator functions. In this approach, a goroutine is
wrapped in a function which generates values that are sent via a channel returned by the
function. The consumer goroutine receives these values as they are generated.

Example extracted from Go Design Patterns For Real-World

package main

import (
	&quot;fmt&quot;
	&quot;strings&quot;
	)

func main() {
	data := []string{&quot;Sphinx of black quartz, judge my vow&quot;, 
			 &quot;The sky is blue and the water too&quot;, 
			 &quot;Cozy lummox gives smart squid who asks for job pen&quot;,
			 &quot;Jackdaws love my big sphinx of quartz&quot;,
			 &quot;The quick onyx goblin jumps over the lazy dwarf&quot;}
	histogram := make(map[string]int)
	words := words(data) // returns handle to data channel
	for word := range words { // Reads each word from channel every time
		histogram[word]++
	}	
	fmt.Println(histogram)
}

// Generator function that produces data
func words(data []string) &lt;-chan string {
	out := make(chan string)
	// Go Routine
	go func() {
		defer close(out) // closes channel upon fn return
		for _, line := range data {
			words := strings.Split(line, &quot; &quot;)
			for _, word := range words {
				word = strings.ToLower(word)
				out &lt;- word // Send word to channel 
			}
		}
	 }()
	 return out
}

https://play.golang.org/p/f0nynFWbEam

In this example, the generator function, declared as func words(data []string) <-
chan string
, returns a receive-only channel of string elements. The consumer function, in this instance main(), receives the data emitted by the generator function, which is processed using a for…range loop.

An improved version of this design pattern:

https://play.golang.org/p/uyUfz3ALO6J

Adding methods like Next and Error:

type iterator struct {
	valueChan 	&lt;-chan interface{}
	okChan 		&lt;-chan bool
	errChan 	&lt;-chan error
	err		error
}

func (i *iterator) next() (interface{}, bool) {
	var (
		value 	interface{}
		ok 	bool
	)
	value, ok, i.err = &lt;-i.valueChan, &lt;-i.okChan, &lt;-i.errChan
	return value, ok
}

func (i *iterator) error() error {
	return i.err
}

// Generator function that produces data
func NewIterator(data []string) iterator {
	out := make(chan interface{})
	ok := make(chan bool)
	err := make(chan error)
	// Go Routine
	go func() {
		defer close(out) // closes channel upon fn return
		for _, line := range data {
			words := strings.Split(line, &quot; &quot;)
			for _, word := range words {
				word = strings.ToLower(word)
				out &lt;- word // Send word to channel and waits for its reading
				ok &lt;- true
				err &lt;- nil // if there was any error, change its value
			}
		}
		out &lt;- &quot;&quot;
		ok &lt;- false
		err &lt;- nil
	 }()
	
	 return iterator{ out, ok, err, nil }
}

答案10

得分: 0

请注意,如果您不需要并发,请不要使用通道。它们被创建用于组织并发流程。除非您尝试实现一个线程安全的迭代器,但这种实现方式比任何简单实现都要慢10到100倍。更多详细信息请参阅https://stackoverflow.com/questions/19621149/how-are-go-channels-implemented。

我不知道惯用的方式,只是想分享一些您可以遵循的想法。

可能您最喜欢的GitHub集合库已经有一些遍历它们的方式。

同样,您的应用程序可能已经有了函数式风格的Iterator接口,例如hasNext, next := list.Iter()

最好只是遵循您已经有的代码风格。可读性和一致性是您的朋友。

从性能上来说,如果您在循环内部放置了任何重要的工作单元,结果将是相同的。

当您真正需要时,当然,for循环会给您更好的性能。

总之,当您可以时使用for循环,遵循代码风格,并重用您已经拥有的抽象。我选择了函数式风格的小型库,因为我没有依赖或风格限制,并且希望保持一切简单和美好。

英文:

Please, don't use channels if you don't need concurrency. They are created to organize your concurrent flow. Unless you're trying to implement a threadsafe iterator which would be 10 to 100 times slow that any trivial implementation. Check for more details https://stackoverflow.com/questions/19621149/how-are-go-channels-implemented.

I don't know the idiomatic way, just want to share a few ideas you can follow.

Probably your favorite GitHub collections library already has some way to iterate over them.

As well as your application could already have Iterator intercase of functional style approach like hasNext, next := list.Iter().

Would be better to just follow code style you already have. The readability and cosistancy are your friends.

Performance-wise, the results will be the same if you put any significant work unit inside a loop.

And of course, for loop gives you a better performance when you really need it.

In a conclusion - use for loops when you can, follow the code style, and reuse abstractions you already have. I choose the functional style for my little library because I have no dependencies or style limitations, and want to keep everything simple and nice.

答案11

得分: 0

在agilemde.co.uk/libraries.zip中有一个ocliterator.go的实现,以及其他Go版本的标准库组件,如日期和随机数。该迭代器提供了类似Java风格的集合迭代器和JavaScript生成器函数迭代器。(注意,依赖于ocl.go通用库)。

英文:

There is an ocliterator.go implementation at agilemde.co.uk/libraries.zip, along with other Go versions of standard library components such as dates and random numbers. The iterator provides Java-style collection iterators and JavaScript generator function iterators. (NB, depends upon ocl.go general library).

huangapple
  • 本文由 发表于 2012年12月22日 14:08:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/14000534.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定