Goマップ値をキーでソート


94

コードで返されたマップ(トピック関数によって返された)を反復処理するときに、キーが順番に表示されません。

キーを正しい順序で取得/マップをソートして、キーが正しい順序で値が対応するようにするにはどうすればよいですか?

これがコードです。


回答:


157

囲碁ブログ:Goはアクションにマップするには、優れた説明があります。

範囲ループでマップを反復する場合、反復順序は指定されておらず、反復ごとに同じであるとは限りません。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 14

1
Goでは、「スライス」という用語は、Java配列に本質的に類似しているデータ構造を指します。これは単なる用語の問題です。キーの取得については、マップ上で明示的に範囲を指定し、次のようにスライスを作成する必要があります。play.golang.org
p

あ、そう。教えてくれてありがとう さて、それはすべてを印刷し、その後すべてを奇数にしています。play.golang.org/p/K2y3m4Zzqdどうすれば、順序どおりに代替することができますか?
gramme.ninja 14

1
返されたスライスをソートする必要があります(または、戻る前にmapKeysでソートする必要があります)。あなたはsortパッケージをチェックアウトしたいと思うでしょう。
joshlf 2014

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は低いと比較します
  • int、float、stringは、<
  • NaNは非NaN浮動小数点数よりも比較が少ない
  • boolはtrueの前にfalseを比較します
  • 複雑なものは、実際のものと、次に虚数のものを比較する
  • ポインタはマシンアドレスで比較します
  • チャネル値はマシンアドレスで比較します
  • 構造体は各フィールドを順番に比較します
  • 配列は各要素を順番に比較します
  • インターフェースの値は、最初に具象型を表すreflect.Typeによって比較され、次に前のルールで説明された具象値によって比較されます。

マップを印刷するとき、NaNなどの非再帰キー値は以前はと表示されていました<nil>。このリリースでは、正しい値が出力されます。

詳細はこちら


8
これは、fmtパッケージと印刷にのみ適用されるようです。質問は、ソートされたマップを印刷する方法ではなく、マップをソートする方法を尋ねますか?
ティム

1
彼は遊び場のリンクを共有しました。そこで彼は地図を印刷するだけです。
イナンクムス

2

私と同じように、基本的に同じ並べ替えコードを複数の場所で使用したい場合、またはコードの複雑さを抑えたい場合は、並べ替え自体を別の関数に抽象化して、そこに関数を渡すことができます希望する実際の作業(もちろん、各呼び出しサイトで異なります)。

キータイプを持つマップを考えるKと、値型Vとして表され、<K>そして<V>以下、このゴーコードテンプレートのようなものに見える可能性がある一般的なソート機能(あるとして-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、(同じソート順が所望されると仮定して)コードのちょうど2ラインそれぞれ使用を保ちます。

欠点は次のとおりです。

  • ファーストクラスとして関数を使用することに慣れていない人々のために読むのは難しいです
  • 遅くなるかもしれません(パフォーマンスの比較は行っていません)

他の言語にはさまざまな解決策があります:

  • <K>and の使用<V>(キーと値の型を示す)がなじみがあるように見える場合、そのコードテンプレートはC ++テンプレートとひどく異なりません。
  • Clojureおよびその他の言語は、基本的なデータ型としてソートされたマップをサポートしています。
  • Goがrangeファーストクラスの型を作成してカスタムで置き換えることができる方法はわかりませんがordered-rangerange元のコードの代わりに)、他のいくつかの言語は同じことを実行するのに十分強力なイテレータを提供していると思います事。

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にあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.