web-dev-qa-db-ja.com

キーでGoマップ値を並べ替える

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

キーを順番に並べる/マップを並べ替えて、キーが順番どおりになり、値が一致するようにするにはどうすればよいですか?

コード です。

79
gramme.ninja

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

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
}
15
joshlf

ここでのすべての回答には、マップの古い動作が含まれています。 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はlowと比較します
  • int、float、stringの順序は<
  • NaNは、NaN以外の浮動小数点数と比較します
  • boolはtrueの前にfalseを比較します
  • 複素数は実数と虚数を比較します
  • ポインタはマシンアドレスで比較します
  • チャネル値はマシンアドレスで比較します
  • 構造体は各フィールドを順番に比較します
  • 配列は各要素を順番に比較します
  • インターフェイス値は、最初に具体的なタイプを説明するreflect.Typeによって比較され、次に前述のルールで説明されている具体的な値によって比較されます。

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

続きを読む こちら.

5
Inanc Gumus

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

以下の<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(同じ並べ替え順序が必要な場合)で再利用でき、各使用を2行のコードのみに維持します。

欠点は次のとおりです。

  • 関数をファーストクラスとして使用することに慣れていない人にとって読みにくい
  • 遅くなる可能性があります(パフォーマンスの比較は行っていません)

他の言語にはさまざまなソリューションがあります。

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

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

拡張プレイグラウンドの例 複数のマップタイプ。

重要な注意点

すべての場合において、マップとソートされたスライスは、マップfor上のrangeループが終了した瞬間から切り離されます。つまり、ソートロジックの後で、マップを使用する前にマップが変更された場合、問題が発生する可能性があります。 (スレッド/ Goルーチンセーフではありません)。並列Map書き込みアクセスの変更がある場合、書き込みとソートされたforループの周りにミューテックスを使用する必要があります。

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

これらの答えのほとんどは正しいですが、Cスタイルのforループが最も簡単なソリューションであることがよくあります(ただし、キーが一連のintsである場合のみ)。

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