マップからキーのスライスを取得する


230

Goのマップからキーのスライスを取得するより簡単で優れた方法はありますか?

現在、私はマップを反復処理し、キーをスライスにコピーしています:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

19
正解は「いいえ」で、より単純で優れた方法はありません。
dldnh 2018年

回答:


202

例えば、

package main

func main() {
    mymap := make(map[int]string)
    keys := make([]int, 0, len(mymap))
    for k := range mymap {
        keys = append(keys, k)
    }
}

Goで効率を上げるには、メモリ割り当てを最小限に抑えることが重要です。


29
容量ではなく実際のサイズを設定し、完全に追加しないようにする方が少し良いです。詳細については私の回答を参照してください。
Vinay Pai 2015

3
mymapローカル変数ではない(したがって拡大/縮小される可能性がある)場合、これが唯一の適切な解決策であることに注意してください。これによりmymapkeysforループの初期化の間の変更のサイズが大きくなっても、境界の問題。
Melllvar 2017

12
マップは同時アクセスでは安全ではありません。別のgoroutineがマップを変更する可能性がある場合、どちらのソリューションも受け入れられません。
Vinay Pai 2017

@VinayPai複数のゴルーチンからマップから読み取ることはできますが、書き込むことはできません
darethas

@darethasはよくある誤解です。レース検出機能は、1.6以降、この使用にフラグを立てます。リリースノートから:「いつものように、1つのゴルーチンがマップに書き込んでいる場合、他のゴルーチンが同時にマップを読み取ったり書き込んだりすることはできません。ランタイムがこの状態を検出すると、診断が出力され、プログラムがクラッシュします。」 golang.org/doc/go1.6#runtime
Vinay Pai

375

これは古い質問ですが、これが私の2セントです。PeterSOの答えは少し簡潔ですが、効率はやや劣ります。あなたはすでにそれがどれほど大きくなるか知っているので、追加を使う必要すらありません:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

ほとんどの場合、ほとんど違いはありませんが、それほど効果はありません。私のテストでは(1,000,000のランダムなint64キーを持つマップを使用し、各メソッドでキーの配列を10回生成しています)、追加を使用するよりも、配列のメンバーを直接割り当てる方が20%高速です。

容量を設定すると再割り当てがなくなりますが、追加では、追加ごとに容量に達しているかどうかを確認するために、追加の作業を行う必要があります。


46
これは、OPのコードとまったく同じに見えます。私はこれがより良い方法であることに同意しますが、この回答のコードとOPのコードの違いを見逃したのかどうか知りたいです。
Emmaly Wilson、2015

4
良い点は、私はどういうわけか他の答えを見て、私の答えがOPとまったく同じであることを逃したことです。ええと、少なくとも、不必要に追加を使用した場合のペナルティについてはおおよそわかりました:)
Vinay Pai

5
範囲のインデックスを使用しないのはなぜですかfor i, k := range mymap{。そうすればあなたはi ++を必要としませんか?
mvndaai

30
たぶんここに何か足りないかもしれませんが、あなたがそうした場合i, k := range mymapiキーとkなり、マップ内のそれらのキーに対応する値になります。これは、実際にはキーのスライスを入力するのに役立ちません。
Vinay Pai

4
@Alaskaは、1つの一時カウンター変数を割り当てるコストに関心があるが、関数呼び出しのメモリ消費が少ないと考える場合は、関数が呼び出されたときに実際に何が起こるかを理解する必要があります。ヒント:無料で行う魔法の呪文ではありません。現在受け入れられている回答が同時アクセスで安全だと思われる場合は、基本に戻る必要があります:blog.golang.org/go-maps-in-action#TOC_6
Vinay Pai

79

また、「reflect」パッケージから、structの[]Valueメソッドによってタイプ付きのキーの配列を取得することもできます。MapKeysValue

package main

import (
    "fmt"
    "reflect"
)

func main() {
    abc := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }

    keys := reflect.ValueOf(abc).MapKeys()

    fmt.Println(keys) // [a b c]
}

1
これは、マップへの同時アクセスの可能性がある場合は適切なアプローチだと思います。ループ中にマップが大きくなってもパニックにならないでしょう。パフォーマンスについてはよくわかりませんが、appendソリューションよりもパフォーマンスが優れていると思います。
Atila Romero、

@AtilaRomeroこのソリューションに利点があるかどうかはわかりませんが、何らかの目的でリフレクションを使用する場合は、キーを値として直接入力できるため、これはより便利です。
Denis Kreshikhin

10
これをに変換する方法はあり[]stringますか?
Doron Behar、

14

これを行うより良い方法は、使用することappendです:

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}

それ以外では、あなたは運が悪いのです。Goはあまり表現力のある言語ではありません。


10
ただし、元の配列よりも効率は低くなります。appendは複数の割り当てを行って基になる配列を拡張し、呼び出しごとにスライスの長さを更新する必要があります。言うkeys = make([]int, 0, len(mymap))ことは割り当てを取り除くでしょうが、私はそれがまだ遅くなることを期待しています。
Nick Craig-Wood

1
コピーの作成中に誰かがマップを変更した場合、この答えはlen(mymap)を使用するよりも安全です。
Atila Romero、

7

他の回答で説明されている3つの方法について、大まかなベンチマークを作成しました。

明らかに、キーをプルする前にスライスを事前に割り当てる方がappending よりも高速ですが、驚くべきことに、reflect.ValueOf(m).MapKeys()メソッドは後者よりも大幅に遅くなります。

 go run scratch.go
populating
filling 100000000 slots
done in 56.630774791s
running prealloc
took: 9.989049786s
running append
took: 18.948676741s
running reflect
took: 25.50070649s

コードは次のとおりです:https : //play.golang.org/p/Z8O6a2jyfTH (プレイグラウンドで実行すると、時間がかかりすぎると主張して中止されるため、ローカルで実行します。)


2
keysAppend関数では、keys配列の容量をで設定できますmake([]uint64, 0, len(m))。これにより、関数のパフォーマンスが劇的に変わりました。
keithbhunter

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