Di golang, adakah cara yang bagus untuk mendapatkan sepotong nilai dari peta?

90

Jika saya memiliki peta m, apakah ada cara yang lebih baik untuk mendapatkan potongan nilai v lalu

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

Apakah ada fitur bawaan dari sebuah peta? Apakah ada fungsi dalam paket Go, atau apakah ini kode terbaik yang harus dilakukan jika perlu?

masebase
sumber
1
alih-alih '_' di loop for Anda, sebut saja idx dan tinggalkan bisnis idx ++
Peter Agnew
Tidak, dia tidak bisa, ketika Anda menjangkau peta itu mengembalikan kunci, nilai bukan indeks, nilai. Dalam contoh ini, dia menggunakan 1 sebagai kunci pertama dan itu akan membuat indeks pada irisan v salah karena indeks awal akan menjadi 1 bukan nol, dan ketika mencapai 4, indeks tersebut akan berada di luar jangkauan. play.golang.org/p/X8_SbgxK4VX
Popmedic
@Popmedic Sebenarnya, ya dia bisa. cukup ganti _dengan idxdan gunakan idx-1saat menetapkan nilai potongan.
Hewiefreeman
3
@ newplayer66, itu adalah pola yang sangat berbahaya.
Popmedic

Jawaban:

60

Sayangnya tidak ada. Tidak ada cara bawaan untuk melakukan ini.

Sebagai catatan tambahan, Anda dapat menghilangkan argumen kapasitas dalam pembuatan slice Anda:

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

Kapasitas diimplikasikan sama dengan panjang disini.

jimt
sumber
1
Saya pikir ada cara yang lebih baik: stackoverflow.com/a/61953291/1162217
Lukas Lukac
58

Sebagai tambahan untuk postingan jimt:

Anda juga dapat menggunakan appenddaripada secara eksplisit menetapkan nilai ke indeksnya:

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

Perhatikan bahwa panjangnya adalah nol (belum ada elemen yang ada) tetapi kapasitas (ruang yang dialokasikan) diinisialisasi dengan jumlah elemen m. Ini dilakukan agar appendtidak perlu mengalokasikan memori setiap kali kapasitas slice vhabis.

Anda juga dapat makememotong tanpa nilai kapasitas dan membiarkan appendmengalokasikan memori untuk dirinya sendiri.

nemo
sumber
Saya bertanya-tanya apakah ini akan lebih lambat (dengan asumsi alokasi depan)? Saya melakukan benchmark kasar dengan map [int] int dan sepertinya 1-2% lebih lambat. Adakah ide jika ini adalah sesuatu yang perlu dikhawatirkan atau hanya pergi bersamanya?
masebase
1
Saya akan menganggap append menjadi sedikit lebih lambat tetapi perbedaan itu, dalam banyak kasus, dapat diabaikan. Tolok ukur membandingkan penugasan langsung dan lampiran .
nemo
1
Hati-hati mencampur ini dengan jawaban di atas - menambahkan ke array setelah Anda gunakan make([]appsv1.Deployment, len(d))akan menambahkan sekelompok elemen kosong yang dibuat saat Anda mengalokasikan len(d)item kosong.
Anirudh Ramanathan
2

Belum tentu lebih baik, tetapi cara bersih untuk melakukan ini adalah dengan mendefinisikan kedua PANJANG Slice and KAPASITAS sepertitxs := 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)
    }

Contoh lengkap:

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
}

Solusi sederhana tapi banyak gotcha. Baca posting blog ini untuk lebih jelasnya: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Lukas Lukac
sumber
Jawabannya tidak "salah". Pertanyaannya menggunakan indeks dan tidak ada tambahan untuk potongan tersebut. Jika Anda menggunakan append pada slice maka ya pengaturan panjang di depan akan berdampak buruk. Ini pertanyaannya karena play.golang.org/p/nsIlIl24Irn Memang pertanyaan itu tidak idiomatis pergi dan saya masih belajar. Versi yang sedikit lebih baik yang Anda bicarakan adalah play.golang.org/p/4SKxC48wg2b
masebase
1
Hai @masebase, saya menganggapnya "salah" karena menyatakan: "Sayangnya, tidak. Tidak ada cara bawaan untuk melakukan ini.", Tetapi ada solusi "yang lebih baik" yang kami tunjukkan sekarang 2 tahun kemudian - menggunakan lampiran () dan menentukan panjang dan kapasitas. Tetapi saya mengerti maksud Anda bahwa menyebutnya "salah" juga tidak akurat. Saya akan mengubah kalimat pertama saya menjadi: "Belum tentu lebih baik, tetapi cara yang lebih bersih untuk melakukannya adalah". Saya telah berbicara dengan ~ 10 pengembang dan semua orang setuju append () adalah cara yang lebih bersih untuk mengonversi peta menjadi potongan tanpa menggunakan indeks pembantu. Saya mempelajarinya juga saat memposting
Lukas Lukac
1

Sejauh yang saya ketahui, go tidak memiliki metode cara untuk menggabungkan string / byte ke string yang dihasilkan tanpa membuat setidaknya / dua / salinan.

Saat ini Anda harus menumbuhkan [] byte karena semua nilai string adalah const, MAKA Anda harus menggunakan string bawaan agar bahasa membuat objek string 'diberkati', yang akan menyalin buffer karena sesuatu di suatu tempat dapat memiliki referensi ke alamat yang mendukung [] byte.

Jika sebuah [] byte cocok maka Anda bisa mendapatkan sedikit petunjuk atas fungsi bytes.Join dengan membuat satu alokasi dan melakukan copy memanggil diri Anda sendiri.

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)
}
Michael J. Evans
sumber
1

Anda dapat menggunakan mapspaket ini :

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

Maka yang harus Anda hubungi adalah

values := maps.GetValuesIntString(m)

Ini jenis-aman untuk mapkombinasi umum itu . Anda dapat generatefungsi aman jenis lain untuk jenis lain yang mapmenggunakan mapperalat dalam paket yang sama.

Pengungkapan penuh: Saya pencipta paket ini. Saya membuatnya karena saya mendapati diri saya menulis ulang fungsi ini mapberulang kali.

Chris Redford
sumber