如何在Go语言中创建一个一流的地图迭代器?

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

How can I create a first-class map iterator in Go?

问题

我正在编写一个函数,用于迭代映射中的条目。我希望能够像for k, v := range myMap { //...那样,在迭代时能够干净地处理添加或删除的条目,但是我每次迭代只处理一个键/值对,所以不能使用range。我想要这样的功能:

func processItem(i iterator) bool {
     k, v, ok := i.next()
     if(!ok) {
         return false
     }
     process(v)
     return true
}

var m = make(map[string]widget)
// ...
i := makeIterator(m)
for processItem(i) {
    // 可能在这里添加/删除m中的条目的代码
}

我知道range使用了一个在src/runtime/hashmap.go中定义的hiter结构和相关函数来执行迭代。有没有办法以一种具体化(first-class)的Go对象的形式访问这个迭代器?

有没有一种替代策略可以很好地处理映射的迭代、插入和删除,并提供一个具体化的迭代器对象?

**附加问题:**有没有一种替代策略可以迭代映射,并且还可以将映射和迭代器序列化到磁盘,然后恢复,继续从上次停止的地方进行迭代?(显然,内置的range迭代器没有这个功能!)

英文:

I am writing a function that iterates over the entries in a map. I want to be able to deal cleanly with items which are added or deleted from the map while iterating, like for k, v := range myMap { //... does, but I am processing just one key/value pair per iteration so I can't use range. I want something like:

func processItem(i iterator) bool {
     k, v, ok := i.next()
     if(!ok) {
         return false
     }
     process(v)
     return true
}

var m = make(map[string]widget)
// ...
i := makeIterator(m)
for processItem(i) {
    // code which might add/remove item from m here
}

I know that range is using a 'hiter' struct and associated functions, as defined in src/runtime/hashmap.go, to perform iteration. Is there some way to gain access to this iterator as a reified (first-class) Go object?

Is there an alternative strategy for iterating over a map which would deal well with insertions/deletions but give a first-class iterator object?

Bonus question: is there an alternative strategy for iterating over a map which could also deal with the map and iterator being serialised to disk and then restored, with iteration continuing from where it left off? (Obviously the built-in range iterator does not have this capability!)

答案1

得分: 6

你无法 如何在Go语言中创建一个一流的地图迭代器?

唯一的遍历map的方法是使用for range,但你无法从中获取迭代器对象。

英文:

You can't 如何在Go语言中创建一个一流的地图迭代器?

The only way to iterate over a map is by using for range and you can't get an iterator object out of that.

答案2

得分: 4

你可以将通道用作迭代器。

你的迭代器可以是一个返回通道的函数,该通道将当前迭代值传递给接收者:

func iterator(m map[string]widget) chan iteration {
    c := make(chan iteration)
    go func() {
        for k, v := range m {
            c <- iteration{k, v}
        }
        close(c)
    }()
    return c
}

当然,这并不是通用的。如果你确实需要通用性,可以使用interface{}和/或反射来实现,但如果你真的需要的话,这应该不难。
在迭代结束时关闭通道将通知迭代的结束,稍后会进行演示。

iteration类型只是为了让你可以同时发送键和值,它可能如下所示:

type iteration struct {
    key   string
    value widget
}

有了这个,你可以这样做(在playground上):

m := map[string]widget{"foo": widget{3}, "bar": widget{4}}
i := iterator(m)

iter, ok := <-i
fmt.Println(iter, ok)
iter, ok = <-i
fmt.Println(iter, ok)
iter, ok = <-i
fmt.Println(iter, ok)

输出结果为:

{foo {3}} true
{bar {4}} true
{ {0}} false
英文:

You can use channels as iterators.

Your iterator would be a function returning a channel that communicates the current iteration value to whoever receives it:

func iterator(m map[string]widget) chan iteration {
	c := make(chan iteration)
	go func() {
		for k,v := range m {
			c &lt;- iteration{k,v}
		}
		close(c)
	}()
	return c
}

This is of course not generic, you could make it generic using interface{} and/or reflection but that shouldn't be too hard if you actually need it.
Closing the channel at the end of iteration will notify the end of iteration, demonstrated later.

The iteration type is just there so you can send key and value at the same time, it would look something like this:

type iteration struct {
	key string
	value widget
}

With this you can then do this (on play):

m := map[string]widget{&quot;foo&quot;: widget{3}, &quot;bar&quot;: widget{4}}
i := iterator(m)

iter, ok := &lt;- i
fmt.Println(iter, ok)
iter, ok = &lt;- i
fmt.Println(iter, ok)
iter, ok = &lt;- i
fmt.Println(iter, ok)

which yields

{foo {3}} true
{bar {4}} true
{ {0}} false

答案3

得分: 4

一个非常简单的方法是获取映射中所有键的列表,并将列表和映射封装在一个迭代器结构中。当我们想要下一个键时,我们从列表中取出尚未从映射中删除的键:

type iterator struct {
    m    map[string]widget
    keys []string
}

func newIterator(m map[string]widget) *iterator {
    it := iterator{m, make([]string, len(m))}
    i := 0
    for k, _ := range m {
        it.keys[i] = k
        i++
    }
    return &it
}

func (it *iterator) next() (string, widget, bool) {
    for len(it.keys) > 0 {
        k := it.keys[0]
        it.keys = it.keys[1:]
        if _, exists := it.m[k]; exists {
            return k, it.m[k], true
        }
    }
    return "", widget{0}, false
}

在playground上查看运行示例。

英文:

A very simple approach is to obtain a list of all the keys in the map, and package the list and the map up in an iterator struct. When we want the next key, we take the next one from the list that hasn't been deleted from the map:

type iterator struct {
	m    map[string]widget
	keys []string
}

func newIterator(m map[string]widget) *iterator {
	it := iterator{m, make([]string, len(m))}
	i := 0
	for k, _ := range m {
		it.keys[i] = k
		i++
	}
	return &amp;it
}

func (it *iterator) next() (string, widget, bool) {
	for len(it.keys) &gt; 0 {
		k := it.keys[0]
		it.keys = it.keys[1:]
		if _, exists := it.m[k]; exists {
			return k, it.m[k], true
		}
	}
	return &quot;&quot;, widget{0}, false
}

See running example on play.

答案4

得分: 0

你可以定义自己的地图类型。解决并发问题也是很好的:

type ConcurrentMap struct {
    sync.RWMutex
    items map[string]interface{}
}

type ConcurrentMapItem struct {
    Key   string
    Value interface{}
}

func (cm *ConcurrentMap) Iter() <-chan ConcurrentMapItem {
    c := make(chan ConcurrentMapItem)

    f := func() {
        cm.Lock()
        defer cm.Unlock()

        for k, v := range cm.items {
            c <- ConcurrentMapItem{k, v}
        }
        close(c)
    }
    go f()

    return c
}

这段代码定义了一个名为ConcurrentMap的结构体,它包含了一个sync.RWMutex类型的字段和一个map[string]interface{}类型的字段。另外还定义了一个名为ConcurrentMapItem的结构体,它包含了KeyValue两个字段。

ConcurrentMap结构体还定义了一个名为Iter的方法,该方法返回一个只读通道<-chan ConcurrentMapItem。在该方法内部,首先创建了一个通道c,然后定义了一个匿名函数f。在f函数内部,通过调用cm.Lock()cm.Unlock()方法来保证并发安全性。然后使用range循环遍历cm.items中的键值对,并将每个键值对发送到通道c中。最后关闭通道c。最后,通过使用go关键字在一个新的goroutine中执行f函数,并返回通道c

这样,你就可以使用ConcurrentMap类型来创建一个并发安全的地图,并通过调用Iter方法来遍历地图中的键值对。

英文:

You can define your own map type. Also it will be good to solve concurrency problem:

type ConcurrentMap struct {
    sync.RWMutex
    items map[string]interface{}
}

type ConcurrentMapItem struct {
    Key   string
    Value interface{}
}

func (cm *ConcurrentMap) Iter() &lt;-chan ConcurrentMapItem {
    c := make(chan ConcurrentMapItem)

    f := func() {
        cm.Lock()
        defer cm.Unlock()

        for k, v := range cm.items {
            c &lt;- ConcurrentMapItem{k, v}
        }
        close(c)
    }
    go f()

    return c
}

huangapple
  • 本文由 发表于 2017年4月5日 00:46:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/43213129.html
匿名

发表评论

匿名网友

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

确定