Harita değerlerini anahtarlara göre sırala


96

Konu işlevi tarafından döndürülen koddaki döndürülen harita üzerinde yineleme yaparken, tuşlar sırayla görünmüyor.

Anahtarların, anahtarların sıralı olması ve değerlerin birbirine uyması için haritayı sıralamak / sıralamak için anahtarları nasıl alabilirim?

İşte kod .


Yanıtlar:


162

Git günlüğü: Git eylem eşleyen mükemmel bir açıklaması var.

Aralık döngüsüne sahip bir harita üzerinde yineleme yaparken, yineleme sırası belirtilmez ve bir yinelemeden diğerine aynı olacağı garanti edilmez. Go 1'den bu yana, programcılar önceki uygulamanın kararlı yineleme sırasına güvendikleri için çalışma zamanı harita yineleme sırasını rastgele seçer. Kararlı bir yineleme sırasına ihtiyacınız varsa, bu sırayı belirten ayrı bir veri yapısı sağlamanız gerekir.

Örnek kodun değiştirilmiş sürümü şu şekildedir : 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])
    }
}

Çıktı:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c

42
Bu, ile geliştirilebilir keys := make([]int, len(m))ve daha sonrakeys[i] = kappend
jpillora

18

Göre Git spec , bir harita üzerinde yineleme sırası tanımlanmamıştır ve programın çalışır arasında değişebilir. Pratikte, sadece tanımsız değil, aslında kasıtlı olarak rastgele hale getirilmiştir. Bunun nedeni, eskiden tahmin edilebilir olması ve Go dili geliştiricilerinin, insanların belirtilmemiş davranışlara güvenmelerini istememeleri, bu nedenle bu davranışa güvenmenin imkansız olması için kasıtlı olarak rastgele hale getirmeleridir.

O zaman yapmanız gereken şey, anahtarları bir dilime çekmek, sıralamak ve sonra dilim üzerinde şu şekilde dolaşmak:

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
}

bekleme dilimleri bir dizinin parçalarıdır. Haritadaki yalnızca anahtarlardan nasıl bir dilim yapabilirim?
gramme.ninja

1
Go'da "dilim" kelimesi, temelde Java dizilerine benzeyen bir veri yapısını ifade eder. Bu sadece bir terminoloji sorunu. Anahtarları almaya gelince, harita üzerinde açıkça dolaşmanız
joshlf

Ah tamam. Bana öğrettiğin için teşekkürler. Şimdi, hepsi, sonra hepsi tuhaf yazdırıyor. play.golang.org/p/K2y3m4Zzqd Sıralı olması için onu nasıl değiştirebilirim?
gramme.ninja

1
Geri aldığınız dilimi sıralamanız (veya alternatif olarak, geri dönmeden önce bunu mapKeys'de sıralamanız) gerekir. Sıralama paketini kontrol etmek isteyeceksiniz .
joshlf

15

Buradaki tüm cevaplar artık haritaların eski davranışını içeriyor. Go 1.12+ sürümünde, sadece bir harita değeri yazdırabilirsiniz ve bu, tuşa göre otomatik olarak sıralanacaktır. Bu, harita değerlerinin kolayca test edilmesine izin verdiği için eklenmiştir.

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

Haritalar, testi kolaylaştırmak için artık anahtar sıralı olarak yazdırılıyor. Sipariş kuralları şunlardır:

  • Mümkün olduğunda, sıfır düşük
  • ints, floats ve string sıralama <
  • NaN, NaN olmayan şamandıralardan daha azını karşılaştırır
  • bool, doğrudan önce yanlışı karşılaştırır
  • Karmaşık, önce gerçek sonra hayali karşılaştırır
  • İşaretçiler makine adresine göre karşılaştırır
  • Kanal değerleri makine adresine göre karşılaştırılır
  • Yapılar sırayla her alanı karşılaştırır
  • Diziler sırayla her bir öğeyi karşılaştırır
  • Arayüz değerleri önce yansıtma ile karşılaştırılır. Somut türü açıklayan tip ve daha sonra önceki kurallarda açıklandığı gibi somut değer ile karşılaştırılır

Haritaları yazdırırken, NaN gibi dönüşlü olmayan anahtar değerleri önceden olarak görüntüleniyordu <nil>. Bu sürümden itibaren doğru değerler yazdırılır.

Daha fazlasını buradan okuyun .


9
Bu sadece fmt paketi ve yazdırma için geçerli görünüyor. Soru, sıralı bir haritanın nasıl yazdırılacağını değil, bir haritanın nasıl sıralanacağını sorar?
Tim

1
Bir oyun alanı bağlantısını paylaştı. Orada sadece bir harita basar.
İnanç Gümüş

2

James Craig Burley'in cevabına yanıt olarak . Temiz ve yeniden kullanılabilir bir tasarım yapmak için daha nesne odaklı bir yaklaşım tercih edilebilir. Bu şekilde yöntemler, belirtilen haritanın türlerine güvenli bir şekilde bağlanabilir. Bana göre bu yaklaşım daha temiz ve düzenli geliyor.

Misal:

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

Birden çok harita türü ile genişletilmiş oyun alanı örneği .

Önemli Not

Her durumda, harita ve sıralanan dilim for, harita üzerindeki döngünün bittiği andan itibaren ayrılır range. Yani, harita sıralama mantığından sonra değiştirilirse, ancak siz onu kullanmadan önce başınız belaya girebilir. (İş parçacığı değil / Rutin güvenli git). Paralel Harita yazma erişiminde bir değişiklik varsa, yazmalar ve sıralanmış fordöngü etrafında bir muteks kullanmanız gerekecektir .

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

1

Benim gibi, birden fazla yerde aynı sıralama kodunu istediğinizi fark ederseniz veya sadece kod karmaşıklığını düşük tutmak istiyorsanız, sıralamanın kendisini ayrı bir işleve aktararak bunu yapan işlevi aktarabilirsiniz. istediğiniz fiili çalışma (tabii ki her arama sitesinde farklı olacaktır).

Anahtar türüne sahip bir harita Verilen Kve değer türü Volarak temsil, <K>ve <V>aşağıda bu git-kod şablonuna benzeyebilecek ortak sıralama fonksiyonu (olduğu gibi git sürüm 1 desteklemediği):

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

Sonra onu girdi eşlemi ve (k <K>, v <V>)eşleme elemanları üzerinden sıralı anahtar sırasına göre çağrılan bir işlev ( girdi argümanlarını alarak) ile çağırın .

Dolayısıyla, Mingu tarafından gönderilen cevaptaki kodun bir versiyonu şöyle görünebilir:

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()Fonksiyonu, herhangi bir için kullanılan yeniden olabilir map[int]stringkod sadece iki satır, her kullanım tutulması, (aynı sıralama düzeni arzu edilir varsayılarak).

Olumsuz yönler şunları içerir:

  • Fonksiyonları birinci sınıf olarak kullanmaya alışkın olmayan insanlar için okumak daha zor
  • Daha yavaş olabilir (performans karşılaştırmaları yapmadım)

Diğer dillerin çeşitli çözümleri vardır:

  • Kullanımı ise <K>ve <V>(anahtar ve değer için göstermektedirler türlerine) biraz tanıdık geliyor, bu kod şablonu C ++ şablonları aksine korkunç değil.
  • Clojure ve diğer diller, temel veri türleri olarak sıralı haritaları destekler.
  • Go'nun rangebir gelenekle ordered-range( rangeorijinal kod yerine) ikame edilebilecek birinci sınıf bir tür ürettiğini bilmesem de, diğer bazı dillerin aynı şeyi başarmak için yeterince güçlü yineleyiciler sağladığını düşünüyorum. şey.

2
Yeni başlayanlar için <K>, <V> sözdiziminin Go'da desteklenmediğini belirtmekte fayda var.
justinhj

Kodunuz şunu belirtir: Görünüşe göre Go, bir haritanın değeri (veya anahtarı) olarak "arayüz {}" ü desteklemiyor / izin vermiyor . Bu doğru değil. var m map[interface{}]interface{}tamamen yasaldır. Oyun Alanı
Tim

Aslında, bu yorum bloğu durumları 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. Eğer (kullanmak unsafeveya benzerini kullanmak dışında ) rasgele bir türün çalışma zamanında nasıl ikame edilebileceğini gösterebilir ve böylece tek bir genel sıralama rutini sağlarsanız, bu harika olurdu! (Aylar önce kendim çözemedim.)
James Craig Burley

0

Bu size haritayı sıralama ile ilgili kod örneği sağlar. Temelde sağladıkları şu:

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

ve bunun yerine kullanmayı önerdiğim şey şu :

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

Tam kod bu Go Playground'da bulunabilir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.