golangには、マップから値のスライスを取得するための優れた方法がありますか?


89

マップmがある場合、値vのスライスを取得するためのより良い方法があります。

package main
import (
  "fmt"
)

func main() {
    m := make(map[int]string)

    m[1] = "a"
    m[2] = "b"
    m[3] = "c"
    m[4] = "d"

    // Can this be done better?
    v := make([]string, len(m), len(m))
    idx := 0
    for  _, value := range m {
       v[idx] = value
       idx++
    }

    fmt.Println(v)
 }

マップの構築された機能はありますか?Goパッケージに関数はありますか、それとも必要な場合はこれが最善のコードですか?


1
forループの「_」の代わりにidxと呼び、idx ++ビジネスを捨てる
Peter Agnew 2017年

いいえ、彼はできません。マップ上で範囲を指定すると、キー、インデックスではなく値、値が返されます。彼の例では、最初のキーとして1を使用します。これにより、開始インデックスがゼロではなく1になり、4に達すると範囲外になるため、スライスvのインデックスが正しくなくなります。play.golang.org/p/X8_SbgxK4VX
Popmedic

@Popmedic実際、そうです。スライス値を割り当てるときに置き換え_idx使用するだけidx-1です。
hewiefreeman

3
@ newplayer66、それは非常に危険なパターンです。
Popmedic

回答:


60

残念だけど違う。これを行うための組み込みの方法はありません。

補足として、スライスの作成では容量引数を省略できます。

v := make([]string, len(m))

容量は、ここでの長さと同じであることを意味します。


もっと良い方法があると思います:stackoverflow.com/a/61953291/1162217
Lukas Lukac

58

jimtの投稿への追加として:

appendインデックスに値を明示的に割り当てるのではなく、を使用することもできます。

m := make(map[int]string)

m[1] = "a"
m[2] = "b"
m[3] = "c"
m[4] = "d"

v := make([]string, 0, len(m))

for  _, value := range m {
   v = append(v, value)
}

長さはゼロ(要素はまだ存在しません)ですが、容量(割り当てられたスペース)はの要素数で初期化されることに注意してくださいm。これはappend、スライスの容量がなくなるたびにメモリを割り当てる必要がないようにするためvです。

make容量値なしでスライスしてappend、それ自体にメモリを割り当てることもできます。


これはもっと遅くなるのではないかと思っていました(事前の割り当てを想定)?map [int] intを使用して大まかなベンチマークを実行しましたが、約1〜2%遅いようでした。これが心配なことなのか、それとも一緒に行くのか、何かアイデアはありますか?
masebase 2012年

1
追加は少し遅いと思いますが、ほとんどの場合、その違いはごくわずかです。直接割り当てと追加を比較するベンチマーク
nemo 2012年

1
これを上記の答えと慎重に混ぜ合わせてください。使用後に配列にmake([]appsv1.Deployment, len(d))追加すると、len(d)空のアイテムを割り当てたときに作成された一連の空の要素が追加されます。
AnirudhRamanathan19年

1

私が現在知っている限り、goには、少なくとも/ two /コピーを作成せずに、文字列/バイトを結果の文字列に連結する方法がありません。

現在、すべての文字列値がconstであるため、[]バイトを増やす必要があります。次に、組み込みの文字列を使用して、言語に「blessed」文字列オブジェクトを作成させる必要があります。これにより、どこかに参照がある可能性があるため、バッファがコピーされます。 []バイトを支えるアドレスに。

[]バイトが適切な場合は、バイトに対してごくわずかなリードを得ることができます。1つの割り当てを行い、コピーを実行することで関数を結合すると、自分自身が呼び出されます。

package main
import (
  "fmt"
)

func main() {
m := make(map[int]string)

m[1] = "a" ;    m[2] = "b" ;     m[3] = "c" ;    m[4] = "d"

ip := 0

/* If the elements of m are not all of fixed length you must use a method like this;
 * in that case also consider:
 * bytes.Join() and/or
 * strings.Join()
 * They are likely preferable for maintainability over small performance change.

for _, v := range m {
    ip += len(v)
}
*/

ip = len(m) * 1 // length of elements in m
r := make([]byte, ip, ip)
ip = 0
for  _, v := range m {
   ip += copy(r[ip:], v)
}

// r (return value) is currently a []byte, it mostly differs from 'string'
// in that it can be grown and has a different default fmt method.

fmt.Printf("%s\n", r)
}

1

このmapsパッケージを使用できます:

go get https://github.com/drgrib/maps

その後、あなたが呼び出す必要があるのは

values := maps.GetValuesIntString(m)

その一般的なmap組み合わせに対してタイプセーフです。次のことができgenerate、任意の他のタイプの他のタイプセーフな機能をmap使用してmapper、同じパッケージでツールを。

完全開示:私はこのパッケージの作成者です。これらの関数をmap繰り返し書き直していることに気付いたので作成しました。


1

必ずしも良いとは限りませんが、これを行うためのよりクリーンな方法は、スライスの長さと容量の両方を次のように定義することです。txs := make([]Tx, 0, len(txMap))

    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))

    for _, tx := range txMap {
        txs = append(txs, tx)
    }

完全な例:

package main

import (
    "github.com/davecgh/go-spew/spew"
)

type Tx struct {
    from  string
    to    string
    value uint64
}

func main() {
    // Extra touch pre-defining the Map length to avoid reallocation
    txMap := make(map[string]Tx, 3)
    txMap["tx1"] = Tx{"andrej", "babayaga", 10}
    txMap["tx2"] = Tx{"andrej", "babayaga", 20}
    txMap["tx3"] = Tx{"andrej", "babayaga", 30}

    txSlice := getTXsAsSlice(txMap)
    spew.Dump(txSlice)
}

func getTXsAsSlice(txMap map[string]Tx) []Tx {
    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))
    for _, tx := range txMap {
        txs = append(txs, tx)
    }

    return txs
}

簡単な解決策ですが、多くの落とし穴があります。詳細については、このブログ投稿をお読みください:https//web3.coach/golang-how-to-convert-map-to-slice-three-gotchas


答えは「間違っている」ではありません。質問では、スライスにインデックスが使用され、追加は使用されません。スライスに追加を使用する場合、はい、前もって長さを設定するのは悪いでしょう。これがplay.golang.org/p/nsIlIl24Irnであるための質問です 確かに、質問は慣用的なものではなく、私はまだ学んでいました。より多くのあなたが話して何のような少し改良版です play.golang.org/p/4SKxC48wg2b
masebase

1
こんにちは@masebase、「残念ながら、いいえ。これを行うための組み込みの方法はありません。」と記載されているため、「間違っている」と思いますが、2年後に両方が指摘している「より良い」解決策があります-追加を使用して()そして長さと容量の両方を定義します。しかし、それを「間違っている」と呼ぶことも正確ではないというあなたの指摘は確かです。最初の文を「必ずしも良いとは限りませんが、これを行うためのよりクリーンな方法は」に変更します。私は約10人の開発者と話をしましたが、append()は、ヘルパーインデックスを使用せずに、マップをスライスに変換するためのよりクリーンな方法であることに全員が同意しました。投稿の途中でもこれを学びました
Lukas Lukac
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.