Ordina i valori della mappa Go per chiavi


Risposte:


157

Il blog Go: Go maps in action ha un'ottima spiegazione.

Quando si esegue l'iterazione su una mappa con un ciclo di intervallo, l'ordine di iterazione non è specificato e non è garantito che sia lo stesso da un'iterazione all'altra. A partire da Go 1 il runtime randomizza l'ordine di iterazione della mappa, poiché i programmatori facevano affidamento sull'ordine di iterazione stabile dell'implementazione precedente. Se è necessario un ordine di iterazione stabile, è necessario mantenere una struttura dati separata che specifichi tale ordine.

Ecco la mia versione modificata del codice di esempio: 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])
    }
}

Produzione:

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

38
Questo può essere migliorato con keys := make([]int, len(m))e quindi inserire per indice keys[i] = kinvece diappend
jpillora

18

Secondo le specifiche Go , l'ordine di iterazione su una mappa non è definito e può variare tra le esecuzioni del programma. In pratica, non solo è indefinito, ma in realtà è intenzionalmente randomizzato. Questo perché era prevedibile e gli sviluppatori del linguaggio Go non volevano che le persone si affidassero a comportamenti non specificati, quindi lo hanno intenzionalmente randomizzato in modo che fare affidamento su questo comportamento fosse impossibile.

Quello che dovrai fare, quindi, è inserire le chiavi in ​​una sezione, ordinarle e quindi spostarti sulla sezione in questo modo:

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
}

le sezioni di attesa sono parti di un array. Come faccio a creare uno slice delle sole chiavi nella mappa?
gramme.ninja

1
In Go, la parola "slice" si riferisce a una struttura dati che è essenzialmente analoga agli array Java. È solo un problema di terminologia. Per quanto riguarda le chiavi, devi spaziare esplicitamente sulla mappa e costruire una fetta mentre procedi in
joshlf

Ah ok. Grazie per avermi insegnato. Ora, sta stampando tutto anche allora tutto dispari. play.golang.org/p/K2y3m4Zzqd Come posso farlo alternare in modo che sia in ordine?
gramme.ninja

1
Dovrai ordinare la fetta che ricevi (o, in alternativa, ordinarla in mapKeys prima di tornare). Ti consigliamo di controllare il pacchetto di smistamento .
joshlf

14

Tutte le risposte qui ora contengono il vecchio comportamento delle mappe. In Go 1.12+, puoi semplicemente stampare un valore di mappa e verrà ordinato automaticamente per chiave. Questo è stato aggiunto perché consente di testare facilmente i valori delle mappe.

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

Le mappe vengono ora stampate in ordine di chiave per facilitare i test. Le regole di ordinazione sono:

  • Quando applicabile, zero confronta basso
  • int, float e stringhe ordinati per <
  • NaN confronta meno di float non NaN
  • bool confronta false prima di true
  • Complesso confronta reale, quindi immaginario
  • I puntatori vengono confrontati in base all'indirizzo macchina
  • I valori dei canali vengono confrontati in base all'indirizzo macchina
  • Le strutture confrontano ogni campo a turno
  • Gli array confrontano ogni elemento a turno
  • I valori dell'interfaccia vengono confrontati prima per riflettere. Tipo che descrive il tipo di calcestruzzo e poi per valore concreto come descritto nelle regole precedenti.

Durante la stampa di mappe, i valori chiave non riflessivi come NaN venivano precedentemente visualizzati come <nil>. A partire da questa versione, vengono stampati i valori corretti.

Leggi di più qui .


8
Questo sembra applicarsi solo al pacchetto fmt e alla stampa. La domanda chiede come ordinare una mappa, non come stampare una mappa ordinata?
Tim

1
Ha condiviso un link al parco giochi. Là stampa solo una mappa.
Inanc Gumus,

2

Se, come me, scopri di volere essenzialmente lo stesso codice di ordinamento in più di un posto, o vuoi semplicemente mantenere bassa la complessità del codice, puoi astrarre l'ordinamento stesso a una funzione separata, a cui passi la funzione che fa il lavoro effettivo che desideri (che sarebbe diverso in ogni sito della chiamata, ovviamente).

Data una mappa con tipo di chiave Ke tipo di valore V, rappresentata come <K>e di <V>seguito, la funzione di ordinamento comune potrebbe assomigliare a questo modello di codice Go (che Go versione 1 non supporta così com'è):

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

Quindi chiamalo con la mappa di input e una funzione (prendendo (k <K>, v <V>)come argomenti di input) che viene chiamata sugli elementi della mappa in ordine di tasti ordinati.

Quindi, una versione del codice nella risposta pubblicata da Mingu potrebbe essere simile a:

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

La sortedMapIntString()funzione può essere riutilizzata per qualsiasi map[int]string(supponendo che si desideri lo stesso ordinamento), mantenendo ogni utilizzo a sole due righe di codice.

Gli svantaggi includono:

  • È più difficile da leggere per le persone non abituate a usare le funzioni come di prima classe
  • Potrebbe essere più lento (non ho effettuato confronti delle prestazioni)

Altre lingue hanno varie soluzioni:

  • Se l'uso di <K>e <V>(per indicare i tipi per la chiave e il valore) sembra un po 'familiare, quel modello di codice non è molto diverso dai modelli C ++.
  • Clojure e altri linguaggi supportano le mappe ordinate come tipi di dati fondamentali.
  • Sebbene non conosca in alcun modo Go rende rangeun tipo di prima classe tale da poter essere sostituito con un custom ordered-range(al posto del rangecodice originale), penso che alcuni altri linguaggi forniscano iteratori che sono abbastanza potenti da ottenere lo stesso cosa.

2
Può valere la pena sottolineare, per i principianti, che la sintassi <K>, <V> non è supportata in Go.
justinhj

2

In risposta al di James Craig Burley risposta . Per creare un design pulito e riutilizzabile, si potrebbe scegliere un approccio più orientato agli oggetti. In questo modo i metodi possono essere associati in modo sicuro ai tipi della mappa specificata. Per me questo approccio sembra più pulito e organizzato.

Esempio:

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

Esempio di parco giochi esteso con più tipi di mappa.

Nota importante

In tutti i casi, la mappa e la sezione ordinata vengono disaccoppiate dal momento in cui il forciclo sulla mappa rangeè terminato. Significa che, se la mappa viene modificata dopo la logica di ordinamento, ma prima di usarla, puoi metterti nei guai. (Non thread / Vai al sicuro dalla routine). Se c'è una modifica dell'accesso in scrittura alla mappa parallela, sarà necessario utilizzare un mutex attorno alle scritture e al forciclo ordinato .

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

0

Questo fornisce l'esempio di codice sull'ordinamento della mappa. Fondamentalmente questo è ciò che forniscono:

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

e questo è quello che suggerirei di usare invece :

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

Il codice completo può essere trovato in questo Go Playground .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.