Urutkan nilai peta Go berdasarkan kunci

95

Saat melakukan iterasi melalui peta yang dikembalikan dalam kode, yang dikembalikan oleh fungsi topik, kunci tidak muncul secara berurutan.

Bagaimana saya bisa mendapatkan kunci untuk mengatur / mengurutkan peta sehingga kunci-kunci tersebut berurutan dan nilainya sesuai?

Ini kodenya .

gramme.ninja
sumber

Jawaban:

159

The Go blog: peta Go beraksi memiliki penjelasan yang sangat baik.

Saat melakukan iterasi pada peta dengan loop rentang, urutan iterasi tidak ditentukan dan tidak dijamin sama dari satu iterasi ke berikutnya. Sejak Go 1, runtime mengacak urutan iterasi peta, karena pemrogram mengandalkan urutan iterasi yang stabil dari implementasi sebelumnya. Jika Anda memerlukan urutan iterasi yang stabil, Anda harus mempertahankan struktur data terpisah yang menentukan urutan itu.

Ini versi kode contoh saya yang dimodifikasi: 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])
    }
}

Keluaran:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c
Mingyu
sumber
41
Ini dapat ditingkatkan dengan keys := make([]int, len(m))dan kemudian masukkan dengan indeks keys[i] = kalih-alihappend
jpillora
18

Menurut spesifikasi Go , urutan iterasi pada peta tidak ditentukan, dan dapat bervariasi antar program berjalan. Dalam praktiknya, tidak hanya tidak ditentukan, itu sebenarnya sengaja diacak. Ini karena dulunya dapat diprediksi, dan pengembang bahasa Go tidak ingin orang mengandalkan perilaku yang tidak ditentukan, jadi mereka sengaja mengacaknya sehingga tidak mungkin mengandalkan perilaku ini.

Apa yang harus Anda lakukan, kemudian, adalah menarik kunci ke dalam irisan, mengurutkannya, dan kemudian menjangkau irisan seperti ini:

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
}
joshlf
sumber
irisan tunggu adalah bagian dari sebuah larik. Bagaimana cara membuat potongan hanya untuk kunci di peta?
gramme.ninja
1
Di Go, kata "slice" mengacu pada struktur data yang pada dasarnya analog dengan array Java. Ini hanya masalah terminologi. Adapun untuk mendapatkan kunci, Anda harus secara eksplisit menjangkau peta dan membuat irisan saat Anda melakukannya seperti ini: play.golang.org/p/ZTni0o19Lr
joshlf
Ah baiklah. Terima kasih telah mengajariku. Sekarang, itu mencetak semua bahkan kemudian semua ganjil. play.golang.org/p/K2y3m4Zzqd Bagaimana cara mengubahnya agar teratur?
gramme.ninja
1
Anda harus mengurutkan potongan yang Anda dapatkan kembali (atau, sebagai alternatif, menyortirnya di mapKeys sebelum kembali). Anda akan ingin memeriksa paket sortir .
joshlf
15

Semua jawaban di sini sekarang berisi perilaku lama peta. Di Go 1.12+, Anda cukup mencetak nilai peta dan akan diurutkan berdasarkan kunci secara otomatis. Ini telah ditambahkan karena memungkinkan pengujian nilai peta dengan mudah.

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

Peta sekarang dicetak dalam urutan kunci untuk memudahkan pengujian. Aturan pemesanannya adalah:

  • Jika berlaku, nol membandingkan rendah
  • ints, floats, dan string yang diurutkan berdasarkan <
  • NaN membandingkan kurang dari float non-NaN
  • bool membandingkan false sebelum true
  • Kompleks membandingkan nyata, kemudian imajiner
  • Pointer dibandingkan dengan alamat mesin
  • Nilai saluran dibandingkan dengan alamat mesin
  • Struktur membandingkan setiap bidang secara bergantian
  • Array membandingkan setiap elemen secara bergantian
  • Nilai antarmuka dibandingkan pertama dengan reflect. Ketik mendeskripsikan tipe konkret dan kemudian dengan nilai konkret seperti yang dijelaskan pada aturan sebelumnya

Saat mencetak peta, nilai kunci non-refleksif seperti NaN sebelumnya ditampilkan sebagai <nil>. Pada rilis ini, nilai yang benar dicetak.

Baca lebih lanjut di sini .

Inanc Gumus
sumber
9
Ini sepertinya hanya berlaku untuk paket fmt dan pencetakan. Pertanyaannya menanyakan bagaimana mengurutkan peta, bukan bagaimana mencetak peta yang diurutkan?
Tim
1
Dia membagikan tautan taman bermain. Di sana dia hanya mencetak peta.
Inanc Gumus
2

Dalam membalas James Craig Burley ini jawabannya . Untuk membuat desain yang bersih dan dapat digunakan kembali, seseorang dapat memilih untuk pendekatan yang lebih berorientasi objek. Metode cara ini dapat dengan aman terikat ke jenis peta yang ditentukan. Bagi saya pendekatan ini terasa lebih bersih dan teratur.

Contoh:

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

Contoh taman bermain yang diperluas dengan beberapa tipe peta.

Catatan penting

Dalam semua kasus, peta dan potongan yang diurutkan dipisahkan dari saat forloop di atas peta rangeselesai. Artinya, jika peta diubah setelah logika pengurutan, tetapi sebelum Anda menggunakannya, Anda bisa mendapat masalah. (Bukan utas / Lakukan pengamanan rutin). Jika ada perubahan akses tulis Peta paralel, Anda harus menggunakan mutex di sekitar penulisan dan forloop yang diurutkan .

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

Jika, seperti saya, Anda merasa menginginkan kode pengurutan yang pada dasarnya sama di lebih dari satu tempat, atau hanya ingin menjaga kerumitan kode, Anda dapat memisahkan pengurutan itu sendiri ke fungsi terpisah, di mana Anda meneruskan fungsi yang melakukannya. pekerjaan aktual yang Anda inginkan (yang akan berbeda di setiap situs panggilan, tentu saja).

Diberikan peta dengan tipe kunci Kdan tipe nilai V, yang direpresentasikan sebagai <K>dan di <V>bawah, fungsi sortir umum mungkin terlihat seperti template Go-code ini (yang Go versi 1 tidak mendukung apa adanya):

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

Kemudian panggil dengan peta masukan dan fungsi (mengambil (k <K>, v <V>)argumen masukannya) yang dipanggil di atas elemen peta dalam urutan tombol yang diurutkan.

Jadi, versi kode dalam jawaban yang diposting oleh Mingu mungkin terlihat seperti ini:

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

The sortedMapIntString()fungsi dapat digunakan kembali untuk setiap map[int]string(dengan asumsi urutan yang sama yang diinginkan), menjaga setiap penggunaan hanya dua baris kode.

Kerugiannya meliputi:

  • Lebih sulit membaca untuk orang yang tidak terbiasa menggunakan fungsi sebagai kelas satu
  • Mungkin lebih lambat (saya belum melakukan perbandingan kinerja)

Bahasa lain memiliki berbagai solusi:

  • Jika penggunaan <K>dan <V>(untuk menunjukkan tipe untuk kunci dan nilai) terlihat agak familiar, template kode tersebut tidak terlalu berbeda dengan template C ++.
  • Clojure dan bahasa lain mendukung peta yang diurutkan sebagai tipe data fundamental.
  • Meskipun saya tidak tahu cara apa pun Go membuat rangetipe kelas satu sehingga dapat diganti dengan custom ordered-range(sebagai pengganti rangedalam kode asli), saya pikir beberapa bahasa lain menyediakan iterator yang cukup kuat untuk mencapai hal yang sama. benda.
James Craig Burley
sumber
2
Mungkin perlu diperhatikan, untuk pemula, bahwa sintaks <K>, <V> tidak didukung di Go.
justinhj
Kode Anda menyatakan: Go tampaknya tidak mendukung / mengizinkan 'antarmuka {}' sebagai nilai (atau kunci) dari sebuah peta . Ini tidak benar. var m map[interface{}]interface{}sepenuhnya legal. Playground
Tim
Sebenarnya, blok komentar itu menyatakan 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. Jika (selain dengan menggunakan unsafeatau serupa) Anda dapat menunjukkan bagaimana jenis arbitrer dapat diganti pada waktu proses, sehingga menyediakan satu jenis rutinitas umum, itu akan bagus! (Saya sendiri tidak bisa mengetahuinya, beberapa bulan yang lalu.)
James Craig Burley
0

Ini memberi Anda contoh kode pada peta pengurutan. Pada dasarnya inilah yang mereka sediakan:

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

dan inilah yang saya sarankan untuk digunakan sebagai gantinya :

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

Kode lengkap dapat ditemukan di Go Playground ini .

Erikas
sumber