按键对Go地图值进行排序


Answers:


157

围棋博客:去映射在行动中有极好的说明。

当使用范围循环在地图上进行迭代时,未指定迭代顺序,并且不能保证每次迭代之间都相同。从Go 1开始,运行时将随机化地图迭代顺序,因为程序员依赖于先前实现的稳定迭代顺序。如果需要稳定的迭代顺序,则必须维护一个指定该顺序的单独数据结构。

这是我对示例代码的修改后的版本: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])
    }
}

输出:

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

38
可以keys := make([]int, len(m))通过使用索引进行改进,然后按索引插入,keys[i] = k而不是append
jpillora

18

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

然后,您要做的就是将键拉入一个切片中,对其进行排序,然后在切片上进行如下排列:

var m map[keyType]valueType
keys := sliceOfKeys(m) // you'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
}

等待片是数组的一部分。如何只对地图中的按键进行切片?
gramme.ninja 2014年

1
在Go中,“切片”一词指的是实质上类似于Java数组的数据结构。这只是一个术语问题。至于获取密钥,您必须像这样去明确地在地图上移动并构造一个切片:play.golang.org/p/ZTni0o19Lr
joshlf

啊好吧。谢谢你教我。现在,它正在打印所有奇数。play.golang.org/p/K2y3m4Zzqd如何才能使其交替显示,以便井井有条?
gramme.ninja 2014年

1
您需要对返回的切片进行排序(或者,也可以在返回之前在mapKeys中对其进行排序)。您将要查看 sort软件包。
joshlf

14

现在,这里的所有答案都包含地图的旧行为。在Go 1.12+中,您只需打印地图值,它将自动按键排序。之所以添加它,是因为它可以轻松测试地图值。

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

现在可以按键排序的顺序打印地图,以简化测试。排序规则是:

  • 适用时,nil比较低
  • 整数,浮点数和字符串按<
  • NaN比非NaN浮点数少
  • 布尔比较假之前为真
  • 复杂比较真实,然后虚构
  • 指针按机器地址比较
  • 通道值按机器地址比较
  • 结构依次比较每个字段
  • 数组依次比较每个元素
  • 接口值首先通过描述具体类型的reflect.Type进行比较,然后再按照先前规则中的具体值进行比较。

打印地图时,非自反键值(如NaN)以前显示为<nil>。从此版本开始,将打印正确的值。

在这里阅读更多


8
这似乎仅适用于fmt打包和打印。问题问如何对地图排序,而不是如何打印排序的地图?
蒂姆(Tim)

1
他分享了一个游乐场链接。在那儿,他只是打印一张地图。
伊南克·古姆斯

2

如果像我一样,发现在多个地方需要基本相同的排序代码,或者只是想降低代码复杂性,则可以将排序本身抽象为一个单独的函数,然后将传递给它的函数传递给该函数。您想要的实际工作(当然,每个呼叫站点会有所不同)。

给定具有键类型K和值类型的映射V(分别表示为<K><V>如下所示),常见的排序函数可能类似于以下Go-code模板(Go版本1不按原样支持):

/* Go apparently doesn't support/allow 'interface{}' 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<K><T>(m map[<K>]<V>, f func(k <K>, v <V>)) {
    var keys []<K>
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)  # or sort.Ints(keys), sort.Sort(...), etc., per <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() {
    // Create a map for processing
    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原始代码),但我认为其他一些语言提供的迭代器也足够强大,可以完成相同的操作事情。

2
对于初学者可能值得指出的是,Go不支持<K>,<V>语法。
justinhj

2

回答詹姆斯·克雷格·伯利的回答。为了进行整洁和可重复使用的设计,可以选择一种面向对象的方法。这样,方法可以安全地绑定到指定映射的类型。对我来说,这种方法更干净,更有条理。

例:

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地图上的循环结束时解耦range。这意味着,如果在排序逻辑之后但在使用映射之前对其进行了修改,则可能会遇到麻烦。(不是线程/ Go例程安全的)。如果并行Map的写访问权限发生了变化,则需要在写操作和排序for循环周围使用互斥体。

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

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中找到。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.