【问题标题】:Collect values in order, each containing a map按顺序收集值,每个值都包含一个映射
【发布时间】:2014-06-13 09:23:11
【问题描述】:

在代码中迭代返回的map时,由topic函数返回,key没有按顺序出现。

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

这里是the code

【问题讨论】:

标签: go hashmap


【解决方案1】:

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

【讨论】:

  • 这可以用keys := make([]int, len(m))改进,然后按索引插入keys[i] = k而不是append
【解决方案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 比较低
  • 整数、浮点数和字符串按 排序
  • NaN 比较小于非 NaN 浮点数
  • bool 在真之前比较假
  • 复数比较实数,然后是虚数
  • 指针按机器地址比较
  • 按机器地址比较通道值
  • 结构体依次比较每个字段
  • 数组依次比较每个元素
  • 接口值首先按描述具体类型的反射.Type 进行比较,然后按前面规则中所述的具体值进行比较。

在打印地图时,像 NaN 这样的非自反键值以前显示为 <nil>。在此版本中,将打印正确的值。

阅读更多here

【讨论】:

  • 这似乎只适用于 fmt 封装和打印。问题是如何对地图进行排序,而不是如何打印已排序的地图?
【解决方案3】:

根据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
}

【讨论】:

  • 等待切片是数组的一部分。如何仅对地图中的键进行切片?
  • 在 Go 中,“切片”一词指的是一种数据结构,它本质上类似于 Java 数组。这只是一个术语问题。至于获取键,您必须明确地在地图上进行范围并像这样构造一个切片:play.golang.org/p/ZTni0o19Lr
  • 啊,好吧。谢谢你教我。现在,它打印的都是偶数,然后都是奇数。 play.golang.org/p/K2y3m4Zzqd我怎样才能让它交替以使其井然有序?
  • 您需要对返回的切片进行排序(或者,在返回之前在 mapKeys 中对其进行排序)。您需要查看sort 包。
【解决方案4】:

回复 James Craig Burley 的 answer。为了进行干净且可重用的设计,人们可能会选择更面向对象的方法。这样,方法可以安全地绑定到指定映射的类型。对我来说,这种方法感觉更干净、更有条理。

例子:

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

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

重要提示

在所有情况下,映射和排序切片从映射 range 上的 for 循环完成的那一刻起就解耦了。这意味着,如果地图在排序逻辑之后被修改,但在你使用它之前,你可能会遇到麻烦。 (不是线程/Go 例程安全的)。如果并行 Map 写入访问发生变化,则需要在写入和排序的 for 循环周围使用互斥锁。

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

【讨论】:

    【解决方案5】:

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

    给定一个键类型为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 &lt;K&gt;, v &lt;V&gt;) 作为其输入参数)调用它,该函数以排序键顺序在映射元素上调用。

    因此,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(假设需要相同的排序顺序),每次使用只需两行代码。

    缺点包括:

    • 对于不习惯将函数用作一流函数的人来说,阅读起来会比较困难
    • 可能会更慢(我没有做过性能比较)

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

    • 如果使用 &lt;K&gt;&lt;V&gt;(表示键和值的类型)看起来有点熟悉,那么该代码模板与 C++ 模板并无太大区别。
    • Clojure 和其他语言支持将排序地图作为基本数据类型。
    • 虽然我不知道 Go 以何种方式使 range 成为一流的类型,以便可以用自定义的 ordered-range 替换(代替原始代码中的 range),但我认为有些其他语言提供了足够强大的迭代器来完成同样的事情。

    【讨论】:

    • 值得指出的是,对于初学者来说,Go 不支持 语法。
    • 您的代码指出:Go 显然不支持/不允许将“接口{}”作为地图的值(或键)。这不是真的。 var m map[interface{}]interface{} 完全合法。 Playground
    • 实际上,该评论块声明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。如果(除了使用unsafe 或类似方法)您可以展示如何在运行时替换任意类型,从而提供一个通用的排序例程,那就太好了! (几个月前我自己也搞不清楚。)
    【解决方案6】:

    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】:

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

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

      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
      }
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-26
        • 2023-02-25
        • 2019-10-26
        • 1970-01-01
        相关资源
        最近更新 更多