按键排序Go地图值

时间:2014-04-28 00:34:31

标签: go hashmap

当迭代主题函数返回的代码中的返回映射时,键不会按顺序出现。

如何让按键对地图进行排序/排序,以便按键顺序且值对应?

这是the code

8 个答案:

答案 0 :(得分:121)

Go blog: Go maps in action有一个很好的解释。

  

在使用范围循环迭代地图时,迭代顺序为   未指定,并且不保证在一次迭代中保持相同   到下一个。从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
    var keys []int
    for k := range m {
        keys = append(keys, k)
    }
    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

答案 1 :(得分:13)

根据Go spec,地图上的迭代顺序是未定义的,并且可能在程序的运行之间有所不同。在实践中,它不仅是未定义的,它实际上是故意随机化的。这是因为它过去是可预测的,并且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
}

答案 2 :(得分:3)

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

鉴于键类型为K且值类型为V的地图,在下面分别表示为<K><V>,常见的排序功能可能类似于以下Go代码模板(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>)作为输入参数)对其进行调用。

因此,answer posted by 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代码),我认为其他一些语言提供的迭代器也足够强大,可以完成同一件事。

答案 3 :(得分:2)

此处所有答案现在都包含地图的旧行为。 在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比较低
  •   
  • 整数,浮点数和字符串按<< / li>的顺序   
  • NaN的比较少于非NaN的浮动
  •   
  • 布尔在真之前先比较假
  •   
  • Complex比较实数,然后是虚数
  •   
  • 指针按机器地址进行比较
  •   
  • 通道值按机器地址进行比较
  •   
  • 结构依次比较每个字段
  •   
  • 数组依次比较每个元素
  •   
  • 接口值首先通过reflect.Type(描述具体类型)进行比较,然后再按照先前规则中的具体值进行比较。
  •   
     

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

了解更多here

答案 4 :(得分:0)

尽管大多数答案都是正确的,但我经常发现C样式的for循环是最简单的解决方案(尽管仅当您的键是一系列int时):

m := make(map[int]string)
m[1] = "a"
m[2] = "c"
m[0] = "b"

for i := 0; i < len(m); i++ {
    fmt.Println("Key:", i, "Value:", m[i])
}

输出:

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

答案 5 :(得分:0)

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

示例:

Whoops, looks like something went wrong.
(1/1) BadMethodCallException

Method paginate does not exist.
in Macroable.php line 74
at Collection->__call('paginate', array(5))in AssetsController.php line 26

Extended playground example具有多种地图类型。

重要提示

在所有情况下,从地图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例程安全的)。如果并行Map的写访问权限发生了变化,则需要在写操作和已排序的range循环周围使用互斥锁。

for

答案 6 :(得分:0)

This为您提供了排序图上的代码示例。基本上这就是他们提供的:

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

完整代码可在this Go Playground中找到。

答案 7 :(得分:0)

当您想要有序的结果时,您似乎不必要地使用地图。您可能想改用切片。

您想使用此代码获得有序结果:

func main() {
    topic := tag("did. the quick, brown yet...")
    for k, v := range topic {
        fmt.Println("key:", k, "v:", v)
    }
}

您在 code 中执行以下操作,自然会得到无序结果:

//                    +-- don't use a map
//                    v
func tag(text string) map[int]map[string]string{
    // ...
    toReturn := make(map[int]map[string]string)

    for i, token := range matches {
        // unnecessarily checking the map...
        m, ok := toReturn[i]
        if !ok{
            m = make(map[string]string)
            toReturn[i] = m
        }
        toReturn[i]["token"] = token
        toReturn[i]["tag"] = "NN"
        // ...
    }
    stopWords := [214]string{
        "a",
        "about",
        // ...
    }
    isStopWord := map[string]bool{}
    for _, sw := range stopWords{
        isStopWord[sw] = true
    }
    for key, mapOne := range toReturn {
        if isStopWord[mapOne["token"]] {
            delete(toReturn, key)
        }
    }
    return toReturn
}

但您可以免费使用切片获得有序结果,like this

//                    +-- use a slice.
//                    +   ordering is free.
//                    v
func tag(text string) []map[string]string {
    // ...
    var toReturn []map[string]string

    for i, token := range matches {
        // append your map to the slice.
        toReturn = append(toReturn, make(map[string]string))
        // ...
    }
    // ...
    for i := len(toReturn) - 1; i >= 0; i-- {
        if m := toReturn[i]; isStopWord[m["token"]] {
            toReturn[i] = toReturn[len(toReturn)-1]
            toReturn = toReturn[:len(toReturn)-1]
        }
    }
    return toReturn
}

始终为工作使用正确的工具。

相关问题