按顺序收集值,每个值都包含一个映射。

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

Collect values in order, each containing a map

问题

在遍历代码中返回的地图时,返回的键没有按顺序出现。

我该如何使键按顺序排列/对地图进行排序,以使键按顺序排列并且值对应?

这是代码

英文:

When iterating through the returned map in the code, returned by the topic function, the keys are not appearing in order.

How can I get the keys to be in order / sort the map so that the keys are in order and the values correspond?

Here is the code.

答案1

得分: 178

Go博客:Go地图实战对此有很好的解释。

> 当使用range循环迭代地图时,迭代顺序未指定,并且不能保证从一次迭代到下一次迭代相同。自Go 1以来,运行时会随机化地图迭代顺序,因为程序员依赖于先前实现的稳定迭代顺序。如果您需要稳定的迭代顺序,您必须维护一个指定该顺序的单独数据结构。

这是我修改后的示例代码:
http://play.golang.org/p/dvqcGPYy3-

package main

import (
	"fmt"
	"sort"
)

func main() {
	// 创建一个作为输入的地图
	m := make(map[int]string)
	m[1] = "a"
	m[2] = "c"
	m[0] = "b"

	// 将键存储在按排序顺序的切片中
	keys := make([]int, len(m))
	i := 0
	for k := range m {
		keys[i] = k
		i++
	}
	sort.Ints(keys)

	// 执行您想要的操作
	for _, k := range keys {
		fmt.Println("键:", k, "值:", m[k])
	}
}

输出:

键: 0 值: b
键: 1 值: a
键: 2 值: c
英文:

The Go blog: Go maps in action has an excellent explanation.

> When iterating over a map with a range loop, the iteration order is
> not specified and is not guaranteed to be the same from one iteration
> to the next. Since Go 1 the runtime randomizes map iteration order, as
> programmers relied on the stable iteration order of the previous
> implementation. If you require a stable iteration order you must
> maintain a separate data structure that specifies that order.

Here's my modified version of example code:
http://play.golang.org/p/dvqcGPYy3-

package main

import (
	"fmt"
	"sort"
)

func main() {
	// To create a map as input
	m := make(map[int]string)
	m[1] = "a"
	m[2] = "c"
	m[0] = "b"

	// To store the keys in slice in sorted order
	keys := make([]int, len(m))
	i := 0
	for k := range m {
		keys[i] = k
		i++
	}
	sort.Ints(keys)

	// To perform the opertion you want
	for _, k := range keys {
		fmt.Println("Key:", k, "Value:", m[k])
	}
}

Output:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c

答案2

得分: 23

所有的答案现在都包含了旧的地图行为。在Go 1.12+中,你只需打印地图值,它将自动按键排序。这是因为它方便地测试地图值。

func main() {
    m := map[int]int{3: 5, 2: 4, 1: 3}
    fmt.Println(m)

    // 在Go 1.12+中
    // 输出: map[1:3 2:4 3:5]

    // 在Go 1.12之前(顺序是未定义的)
    // map[3:5 2:4 1:3]
}

地图现在按键排序的顺序打印,以便于测试。排序规则如下:

  • 如果适用,nil比较低
  • ints、floats和strings按<顺序排序
  • NaN比非NaN浮点数小
  • bool在true之前比较false
  • 复数按实部,然后按虚部排序
  • 指针按机器地址比较
  • 通道值按机器地址比较
  • 结构体按顺序比较每个字段
  • 数组按顺序比较每个元素
  • 接口值首先按照描述具体类型的reflect.Type比较,然后按照前面的规则比较具体值。

在打印地图时,以前显示为&lt;nil&gt;的非反射键值(如NaN)现在显示正确的值。

在此处阅读更多信息(https://tip.golang.org/doc/go1.12#fmt)。

英文:

All of the answers here now contain the old behavior of maps. In Go 1.12+, you can just print a map value and it will be sorted by key automatically. This has been added because it allows the testing of map values easily.

func main() {
    m := map[int]int{3: 5, 2: 4, 1: 3}
    fmt.Println(m)

    // In Go 1.12+
    // Output: map[1:3 2:4 3:5]

    // Before Go 1.12 (the order was undefined)
    // map[3:5 2:4 1:3]
}

> Maps are now printed in key-sorted order to ease testing. The ordering rules are:
>
> * When applicable, nil compares low
> * ints, floats, and strings order by <
> * NaN compares less than non-NaN floats
> * bool compares false before true
> * Complex compares real, then imaginary
> * Pointers compare by machine address
> * Channel values compare by machine address
> * Structs compare each field in turn
> * Arrays compare each element in turn
> * Interface values compare first by reflect.Type describing the concrete type and then by concrete value as described in the previous rules.
>
> When printing maps, non-reflexive key values like NaN were previously displayed as &lt;nil&gt;. As of this release, the correct values are printed.

Read more here.

答案3

得分: 20

根据Go规范,对映射进行迭代的顺序是未定义的,并且可能在程序的不同运行之间变化。实际上,它不仅是未定义的,而且还有意地进行了随机化。这是因为它曾经是可预测的,而Go语言的开发人员不希望人们依赖于未指定的行为,所以他们有意地将其随机化,以确保无法依赖于这种行为。

因此,你需要将键提取到一个切片中,对其进行排序,然后像这样遍历切片:

var m map[keyType]valueType
keys := sliceOfKeys(m) // 你需要实现这个函数
for _, k := range keys {
    v := m[k]
    // k 是键,v 是值;在这里进行你的计算
}
英文:

According to the Go spec, the order of iteration over a map is undefined, and may vary between runs of the program. In practice, not only is it undefined, it's actually intentionally randomized. This is because it used to be predictable, and the Go language developers didn't want people relying on unspecified behavior, so they intentionally randomized it so that relying on this behavior was impossible.

What you'll have to do, then, is pull the keys into a slice, sort them, and then range over the slice like this:

var m map[keyType]valueType
keys := sliceOfKeys(m) // you&#39;ll have to implement this
for _, k := range keys {
    v := m[k]
    // k is the key and v is the value; do your computation here
}

答案4

得分: 4

作为你的中文翻译助手,我将为你翻译以下内容:

回复James Craig Burley的答案。为了实现一个干净且可重用的设计,可以选择更面向对象的方法。这样,方法可以安全地绑定到指定映射的类型上。对我来说,这种方法感觉更加清晰和有组织。

示例:

package main

import (
	"fmt"
	"sort"
)

type myIntMap map[int]string

func (m myIntMap) sort() (index []int) {
	for k, _ := range m {
		index = append(index, k)
	}
	sort.Ints(index)
	return
}

func main() {
	m := myIntMap{
		1:  "one",
		11: "eleven",
		3:  "three",
	}
	for _, k := range m.sort() {
		fmt.Println(m[k])
	}
}

使用多个映射类型的扩展示例

重要提示

在所有情况下,从遍历映射的for循环结束时,映射和排序的切片是解耦的。这意味着,如果在排序逻辑之后但在使用之前修改了映射,可能会出现问题(不是线程/Go协程安全的)。如果存在并行映射写访问的更改,你需要在写入和排序的for循环周围使用互斥锁。

mutex.Lock()
for _, k := range m.sort() {
    fmt.Println(m[k])
}
mutex.Unlock()
英文:

In reply to James Craig Burley's answer. In order to make a clean and re-usable design, one might choose for a more object oriented approach. This way methods can be safely bound to the types of the specified map. To me this approach feels cleaner and organized.

Example:

package main

import (
	&quot;fmt&quot;
	&quot;sort&quot;
)

type myIntMap map[int]string

func (m myIntMap) sort() (index []int) {
	for k, _ := range m {
		index = append(index, k)
	}
	sort.Ints(index)
	return
}

func main() {
	m := myIntMap{
		1:  &quot;one&quot;,
		11: &quot;eleven&quot;,
		3:  &quot;three&quot;,
	}
	for _, k := range m.sort() {
		fmt.Println(m[k])
	}
}

Extended playground example with multiple map types.

Important note

In all cases, the map and the sorted slice are decoupled from the moment the for loop over the map range is finished. Meaning that, if the map gets modified after the sorting logic, but before you use it, you can get into trouble. (Not thread / Go routine safe). If there is a change of parallel Map write access, you'll need to use a mutex around the writes and the sorted for loop.

mutex.Lock()
for _, k := range m.sort() {
    fmt.Println(m[k])
}
mutex.Unlock()

答案5

得分: 2

如果你和我一样,发现你想在多个地方使用基本相同的排序代码,或者只是想保持代码的简洁性,你可以将排序本身抽象为一个单独的函数,并将实际工作的函数传递给它(当然,每个调用点的函数可能是不同的)。

给定一个键类型为K,值类型为V的映射,表示为下面的<K><V>,通用的排序函数可能看起来像这样的Go代码模板(Go版本1不支持原样):

/* Go显然不支持/允许将'interface{}'作为映射的值(或键),以便在运行时可以替换任意类型,因此可能需要几个这些几乎相同的函数来处理不同的键/值类型组合。*/
func sortedMap<K, V>(m map<K, V>, f func(k K, v V)) {
    var keys []K
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)  // 或者sort.Ints(keys),sort.Sort(...),等等,根据<K>的类型
    for _, k := range keys {
        v := m[k]
        f(k, v)
    }
}

然后使用输入映射和一个函数(以(k K, v V)作为其输入参数)调用它,该函数按排序键顺序对映射元素进行调用。

因此,Mingu发布的答案中的代码版本可能如下所示:

package main

import (
    "fmt"
    "sort"
)

func sortedMapIntString(m map[int]string, f func(k int, v string)) {
    var keys []int
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Ints(keys)
    for _, k := range keys {
        f(k, m[k])
    }
}

func main() {
    // 创建一个用于处理的映射
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    sortedMapIntString(m,
        func(k int, v string) { fmt.Println("Key:", k, "Value:", v) })
}

sortedMapIntString()函数可以重复使用于任何map[int]string(假设希望使用相同的排序顺序),每次使用只需两行代码。

缺点包括:

  • 对于不习惯使用函数作为一等公民的人来说,阅读起来更困难。
  • 它可能会更慢(我没有进行性能比较)。

其他语言有各种解决方案:

  • 如果使用<K><V>(用于表示键和值的类型)看起来有点熟悉,那么这个代码模板与C++模板并没有太大不同。

  • Clojure和其他语言支持排序映射作为基本数据类型。

  • 虽然我不知道Go是否使range成为一等类型,以便可以用自定义的ordered-range替换range(原始代码中的range),但我认为其他一些语言提供的迭代器足够强大,可以实现相同的功能。

英文:

If, like me, you find you want essentially the same sorting code in more than one place, or just want to keep the code complexity down, you can abstract away the sorting itself to a separate function, to which you pass the function that does the actual work you want (which would be different at each call site, of course).

Given a map with key type K and value type V, represented as &lt;K&gt; and &lt;V&gt; below, the common sort function might look something like this Go-code template (which Go version 1 does not support as-is):

/* Go apparently doesn&#39;t support/allow &#39;interface{}&#39; as the value (or
/* key) of a map such that any arbitrary type can be substituted at
/* run time, so several of these nearly-identical functions might be
/* needed for different key/value type combinations. */
func sortedMap&lt;K&gt;&lt;T&gt;(m map[&lt;K&gt;]&lt;V&gt;, f func(k &lt;K&gt;, v &lt;V&gt;)) {
	var keys []&lt;K&gt;
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)  # or sort.Ints(keys), sort.Sort(...), etc., per &lt;K&gt;
	for _, k := range keys {
		v := m[k]
		f(k, v)
	}
}

Then call it with the input map and a function (taking (k &lt;K&gt;, v &lt;V&gt;) as its input arguments) that is called over the map elements in sorted-key order.

So, a version of the code in the answer posted by Mingu might look like:

package main

import (
    &quot;fmt&quot;
    &quot;sort&quot;
)

func sortedMapIntString(m map[int]string, f func(k int, v string)) {
	var keys []int
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	for _, k := range keys {
		f(k, m[k])
	}
}

func main() {
    // Create a map for processing
    m := make(map[int]string)
    m[1] = &quot;a&quot;
    m[2] = &quot;c&quot;
    m[0] = &quot;b&quot;

    sortedMapIntString(m,
        func(k int, v string) { fmt.Println(&quot;Key:&quot;, k, &quot;Value:&quot;, v) })
}

The sortedMapIntString() function can be re-used for any map[int]string (assuming the same sort order is desired), keeping each use to just two lines of code.

Downsides include:

  • It's harder to read for people unaccustomed to using functions as first-class
  • It might be slower (I haven't done performance comparisons)

Other languages have various solutions:

  • If the use of &lt;K&gt; and &lt;V&gt; (to denote types for the key and value) looks a bit familiar, that code template is not terribly unlike C++ templates.
  • Clojure and other languages support sorted maps as fundamental data types.
  • While I don't know of any way Go makes range a first-class type such that it could be substituted with a custom ordered-range (in place of range in the original code), I think some other languages provide iterators that are powerful enough to accomplish the same thing.

答案6

得分: 0

这是一个关于排序映射的代码示例。基本上,这是他们提供的代码:

var keys []int
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark1-8   	 2863149	       374 ns/op	     152 B/op	       5 allocs/op

而这是我建议使用的代码

keys := make([]int, 0, len(myMap))
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark2-8   	 5320446	       230 ns/op	      80 B/op	       2 allocs/op

完整的代码可以在这个Go Playground链接中找到。

英文:

This provides you the code example on sorting map. Basically this is what they provide:

var keys []int
for k := range myMap {
	keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark1-8   	 2863149	       374 ns/op	     152 B/op	       5 allocs/op

and this is what I would suggest using instead:

keys := make([]int, 0, len(myMap))
for k := range myMap {
	keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark2-8   	 5320446	       230 ns/op	      80 B/op	       2 allocs/op

Full code can be found in this Go Playground.

huangapple
  • 本文由 发表于 2014年4月28日 08:34:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/23330781.html
匿名

发表评论

匿名网友

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

确定