有没有一种简单的方法按顺序迭代遍历一个映射?

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

Is there an easy way to iterate over a map in order?

问题

这是一个古老的问题的变体:“为什么我的地图打印顺序不对”。

我有很多形式为map[MyKey]MyValue的地图,其中MyKeyMyValue通常是结构体。我为所有键类型都有“less”函数。

我需要按顺序迭代地图。(具体来说,是由该类型上的less函数定义的顺序。)现在,我的代码看起来像这样:

type PairKeyValue struct {
    MyKey
    MyValue
}

type PairKeyValueSlice []Pair

func (ps PairKeyValueSlice) Len() int {
    return len(ps)
}

func (ps PairKeyValueSlice) Swap(i,j int) {
    ps[i], ps[j] = ps[j], ps[i]
}

func (ps PairKeyValueSlice) Less(i,j int) {
    return LessKey(ps[i].MyKey, ps[j].MyKey)
}

func NewPairKeyValueSlice(m map[MyKey]MyValue) (ps PairKeyValueSlice) {
    ps = make(PairKeyValueSlice, len(m))
    i := 0
    for k,v := range m {
        ps[i] = PairKeyValue{k,v}
        i++
    }
    sort.Sort(ps)
}

然后,每当我想要按顺序迭代时,它看起来像:

var m map[MyKey]MyValue
m = GetMapFromSomewhereUseful()
for _, kv := range NewPairKeyValueSlice(m) {
    key := kv.MyKey
    value := kv.MyValue
    DoUsefulWork(key, value)
}

这似乎基本上可以工作。问题是它太冗长了。特别是因为手头的问题与实现有序地图几乎没有关系,而是与循环中的有用工作有关。

此外,我有几个不同的键和值类型。因此,每当我想要按顺序迭代地图时,我都会复制/粘贴所有这些代码,并将MyKey替换为新键,MyValue替换为新值。这种程度的复制/粘贴是...“有问题的”。这已经变得很麻烦,因为我已经犯了几次需要多次修复的错误。

这种技术的缺点是它需要完全复制所有键和值。这是不可取的,但我看不到其他办法。(我可以将其减少到只有键,但它不会改变问题的主要性质。)

这个问题尝试使用字符串做同样的事情。这个问题使用字符串和整数。这个问题暗示您需要使用反射,并且必须有一个在每种可能类型上都进行切换的switch语句,包括所有用户定义的类型。

但是对于那些对地图不确定迭代的人来说,似乎有必须有一个更好的解决方案。我来自面向对象的背景,所以我可能忽略了一些基本的东西。

那么,有没有合理的方法按顺序迭代地图?


**更新:**编辑问题以提供有关源的更多信息,以防有比这更好的解决方案。

我有很多需要分组输出的东西。每个分组级别都在一个类似这样的结构中:

type ObjTypeTree struct {
    Children map[Type]*ObjKindTree
    TotalCount uint
}
type ObjKindTree struct {
    Children map[Kind]*ObjAreaTree
    TotalCount uint
}
type ObjAreaTree struct {
    Children map[Area]*ObjAreaTree
    TotalCount uint
    Objs []*Obj
}

然后,我会迭代ObjTypeTree中的子项以打印类型分组。对于每个子项,我会迭代ObjKindTree以打印种类分组。迭代是通过类型的方法完成的,每种类型的打印分组级别的方式都有所不同。分组需要按顺序打印,这就引发了问题。

英文:

This is a variant of the venerable "why is my map printing out of order" question.

I have a (fairly large) number of maps of the form map[MyKey]MyValue, where MyKey and MyValue are (usually) structs. I've got "less" functions for all the key types.

I need to iterate over the maps in order. (Specifically, the order defined by the less function on that type.) Right now, my code looks like this:

type PairKeyValue struct {
    MyKey
    MyValue
}

type PairKeyValueSlice []Pair

func (ps PairKeyValueSlice) Len() int {
    return len(ps)
}

func (ps PairKeyValueSlice) Swap(i,j int) {
    ps[i], ps[j] = ps[j], ps[i]
}

func (ps PairKeyValueSlice) Less(i,j int) {
    return LessKey(ps[i].MyKey, ps[j].MyKey)
}

func NewPairKeyValueSlice(m map[MyKey]MyValue) (ps PairKeyValueSlice) {
    ps = make(PairKeyValueSlice, len(m))
    i := 0
    for k,v := range m {
        ps[i] = PairKeyValue{k,v}
        i++
    }
    sort.Sort(ps)
}

And then, any time I want an in-order iteration, it looks like:

var m map[MyKey]MyValue
m = GetMapFromSomewhereUseful()
for _, kv := range NewPairKeyValueSlice(m) {
    key := kv.MyKey
    value := kv.MyValue
    DoUsefulWork(key, value)
}

And this appears to largely work. The problem is that it is terribly verbose. Particularly since the problem at hand really has very little to do with implmenting ordered maps and is really about the useful work in the loop.

Also, I have several different keys and value types. So, every time I want to iterate over a map in order, I copy/paste all that code and do find/replace MyKey with the new key and MyValue with the new value. Copy/paste on that magnitude is... "smelly". It has already become a hassle, since I've already made a few errors that I had to fix several times.

This technique also has the downside that it requires making a full copy of all the keys and values. That is undesirable, but I don't see a way around it. (I could reduce it to just the keys, but it doesn't change the primary nature of the problem.)

This question is attempting the same thing with strings. This question does it with strings and ints. This question implies that you need to use reflection and will have to have a switch statement that switches on every possible type, including all user-defined types.

But with the people who are puzzled that maps don't iterate deterministically, it seems that there has got to be a better solution to this problem. I'm from an OO background, so I'm probably missing something fundamental.

So, is there a reasonable way to iterate over a map in order?


Update: Editing the question to have more information about the source, in case there's a better solution than this.

I have a lot of things I need to group for output. Each grouping level is in a structure that looks like these:

type ObjTypeTree struct {
    Children map[Type]*ObjKindTree
    TotalCount uint
}
type ObjKindTree struct {
    Children map[Kind]*ObjAreaTree
    TotalCount uint
}
type ObjAreaTree struct {
    Children map[Area]*ObjAreaTree
    TotalCount uint
    Objs []*Obj
}

Then, I'd iterate over the children in the ObjTypeTree to print the Type groupings. For each of those, I iterate over the ObjKindTree to print the Kind groupings. The iterations are done with methods on the types, and each kind of type needs a little different way of printing its grouping level. Groups need to be printed in order, which causes the problem.

答案1

得分: 2

不要使用映射(map)如果需要键的排序。使用B树或任何其他/类似的有序容器。

英文:

Don't use a map if key collating is required. Use a B-tree or any other/similar ordered container.

答案2

得分: 2

我同意jnml的答案。但是如果你想要比你现在的更短的东西,并且愿意放弃编译时类型安全性,那么我的库可能适合你。 (它是建立在reflect之上的。)这是一个完整的工作示例:

package main

import (
	"fmt"

	"github.com/BurntSushi/ty/fun"
)

type OrderedKey struct {
	L1 rune
	L2 rune
}

func (k1 OrderedKey) Less(k2 OrderedKey) bool {
	return k1.L1 < k2.L1 || (k1.L1 == k2.L1 && k1.L2 < k2.L2)
}

func main() {
	m := map[OrderedKey]string{
		OrderedKey{'b', 'a'}: "second",
		OrderedKey{'x', 'y'}: "fourth",
		OrderedKey{'x', 'x'}: "third",
		OrderedKey{'a', 'b'}: "first",
		OrderedKey{'x', 'z'}: "fifth",
	}

	for k, v := range m {
		fmt.Printf("(%c, %c): %s\n", k.L1, k.L2, v)
	}
	fmt.Println("-----------------------------")

	keys := fun.QuickSort(OrderedKey.Less, fun.Keys(m)).([]OrderedKey)
	for _, k := range keys {
		v := m[k]
		fmt.Printf("(%c, %c): %s\n", k.L1, k.L2, v)
	}
}

请注意,这样的方法会更慢,所以如果你需要性能,这不是一个好选择。

英文:

I second jnml's answer. But if you want something shorter than you have and are willing to give up compile time type safety, then my library might work for you. (It's built on top of reflect.) Here's a full working example:

package main

import (
	&quot;fmt&quot;

	&quot;github.com/BurntSushi/ty/fun&quot;
)

type OrderedKey struct {
	L1 rune
	L2 rune
}

func (k1 OrderedKey) Less(k2 OrderedKey) bool {
	return k1.L1 &lt; k2.L1 || (k1.L1 == k2.L1 &amp;&amp; k1.L2 &lt; k2.L2)
}

func main() {
	m := map[OrderedKey]string{
		OrderedKey{&#39;b&#39;, &#39;a&#39;}: &quot;second&quot;,
		OrderedKey{&#39;x&#39;, &#39;y&#39;}: &quot;fourth&quot;,
		OrderedKey{&#39;x&#39;, &#39;x&#39;}: &quot;third&quot;,
		OrderedKey{&#39;a&#39;, &#39;b&#39;}: &quot;first&quot;,
		OrderedKey{&#39;x&#39;, &#39;z&#39;}: &quot;fifth&quot;,
	}

	for k, v := range m {
		fmt.Printf(&quot;(%c, %c): %s\n&quot;, k.L1, k.L2, v)
	}
	fmt.Println(&quot;-----------------------------&quot;)

	keys := fun.QuickSort(OrderedKey.Less, fun.Keys(m)).([]OrderedKey)
	for _, k := range keys {
		v := m[k]
		fmt.Printf(&quot;(%c, %c): %s\n&quot;, k.L1, k.L2, v)
	}
}

Note that such a method will be slower, so if you need performance, this is not a good choice.

huangapple
  • 本文由 发表于 2013年6月19日 06:09:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/17179880.html
匿名

发表评论

匿名网友

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

确定