简体   繁体   中英

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

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

You can't :(

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

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 <- 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{"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)

which yields

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

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

See running example on play.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM