コードで返されたマップ(トピック関数によって返された)を反復処理するときに、キーが順番に表示されません。
キーを正しい順序で取得/マップをソートして、キーが正しい順序で値が対応するようにするにはどうすればよいですか?
これがコードです。
コードで返されたマップ(トピック関数によって返された)を反復処理するときに、キーが順番に表示されません。
キーを正しい順序で取得/マップをソートして、キーが正しい順序で値が対応するようにするにはどうすればよいですか?
これがコードです。
回答:
囲碁ブログ: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
keys := make([]int, len(m))
、次いで、インデックスによってインサートkeys[i] = k
の代わりappend
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
}
ここでのすべての回答には、マップの古い動作が含まれています。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>
。このリリースでは、正しい値が出力されます。
詳細はこちら。
私と同じように、基本的に同じ並べ替えコードを複数の場所で使用したい場合、またはコードの複雑さを抑えたい場合は、並べ替え自体を別の関数に抽象化して、そこに関数を渡すことができます希望する実際の作業(もちろん、各呼び出しサイトで異なります)。
キータイプを持つマップを考える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 ++テンプレートとひどく異なりません。range
ファーストクラスの型を作成してカスタムで置き換えることができる方法はわかりませんがordered-range
(range
元のコードの代わりに)、他のいくつかの言語は同じことを実行するのに十分強力なイテレータを提供していると思います事。ジェームズ・クレイグ・バーレイの回答に応えて。クリーンで再利用可能なデザインを作るために、よりオブジェクト指向のアプローチを選ぶかもしれません。このようにして、メソッドを指定されたマップのタイプに安全にバインドできます。私にとって、このアプローチはよりすっきりと整理されていると感じます。
例:
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()
これにより、ソートマップのコード例が提供されます。基本的にこれは彼らが提供するものです:
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にあります。