Classificar os valores do mapa Go por chaves

93

Ao iterar por meio do mapa retornado no código, retornado pela função de tópico, as chaves não aparecem em ordem.

Como posso colocar as chaves em ordem / classificar o mapa de modo que as chaves estejam em ordem e os valores correspondam?

Aqui está o código .

gramme.ninja
fonte
Possível duplicata de Como iterar por meio de um mapa no golang em ordem?
RedGrittyBrick

Respostas:

156

O blog Go: Mapas Go em ação tem uma explicação excelente.

Ao iterar em um mapa com um loop de intervalo, a ordem da iteração não é especificada e não é garantida a mesma de uma iteração para a próxima. Desde o Go 1, o tempo de execução torna aleatória a ordem de iteração do mapa, pois os programadores contavam com a ordem de iteração estável da implementação anterior. Se você precisar de uma ordem de iteração estável, deverá manter uma estrutura de dados separada que especifique essa ordem.

Esta é minha versão modificada do código de exemplo: 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])
    }
}

Resultado:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c
Mingyu
fonte
38
Isso pode ser melhorado com keys := make([]int, len(m))e, em seguida, inserir por índice em keys[i] = kvez deappend
jpillora
18

De acordo com a especificação Go , a ordem de iteração em um mapa é indefinida e pode variar entre as execuções do programa. Na prática, não só é indefinido, como também é aleatório intencionalmente. Isso porque costumava ser previsível, e os desenvolvedores da linguagem Go não queriam que as pessoas confiassem em um comportamento não especificado, então eles o randomizaram intencionalmente para que depender desse comportamento fosse impossível.

O que você terá que fazer, então, é puxar as chaves em uma fatia, classificá-las e, em seguida, percorrer a fatia assim:

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
fonte
fatias de espera são partes de uma matriz. Como faço uma fatia apenas das chaves no mapa?
gramme.ninja
1
Em Go, a palavra "slice" se refere a uma estrutura de dados que é essencialmente análoga aos arrays Java. É apenas uma questão de terminologia. Quanto a obter as chaves, você deve percorrer explicitamente o mapa e construir uma fatia à medida que avança, assim: play.golang.org/p/ZTni0o19Lr
joshlf
Ah ok. Obrigado por me ensinar. Agora, está imprimindo tudo par, então tudo estranho. play.golang.org/p/K2y3m4Zzqd Como faço para alternar para que esteja em ordem?
gramme.ninja
1
Você precisará classificar a fatia que recebeu de volta (ou, alternativamente, classifique-a em mapKeys antes de retornar). Você vai querer verificar o pacote de classificação .
joshlf
14

Todas as respostas aqui agora contêm o antigo comportamento dos mapas. No Go 1.12+, você pode simplesmente imprimir um valor do mapa e ele será classificado por chave automaticamente. Isso foi adicionado porque permite o teste dos valores do mapa facilmente.

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

Os mapas agora são impressos em ordem classificada por chave para facilitar o teste. As regras de ordenação são:

  • Quando aplicável, nil se compara a baixo
  • ints, floats e strings ordenados por <
  • NaN compara menos do que flutuadores não NaN
  • bool compara falso antes de verdadeiro
  • Complexo compara real e imaginário
  • Os ponteiros comparam por endereço de máquina
  • Os valores do canal são comparados por endereço da máquina
  • As estruturas comparam cada campo por vez
  • Arrays comparam cada elemento por vez
  • Os valores da interface são comparados primeiro por reflect.Type que descreve o tipo concreto e, em seguida, por valor concreto, conforme descrito nas regras anteriores.

Ao imprimir mapas, valores-chave não reflexivos, como NaN, eram exibidos anteriormente como <nil>. A partir desta versão, os valores corretos são impressos.

Leia mais aqui .

Inanc Gumus
fonte
8
Isso parece se aplicar apenas ao pacote fmt e à impressão. A questão é como classificar um mapa, não como imprimir um mapa classificado.
Tim de
1
Ele compartilhou um link de playground. Lá ele apenas imprime um mapa.
Inanc Gumus de
2

Se, como eu, você descobrir que deseja essencialmente o mesmo código de classificação em mais de um lugar, ou apenas deseja manter a complexidade do código baixa, pode abstrair a própria classificação para uma função separada, para a qual você passa a função que faz o trabalho real que você deseja (que seria diferente em cada local de chamada, é claro).

Dado um mapa com tipo de chave Ke tipo de valor V, representado como <K>e <V>abaixo, a função de classificação comum pode ser semelhante a este modelo Go-code (que Go versão 1 não suporta no estado em que se encontra):

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

Em seguida, chame-o com o mapa de entrada e uma função (tomando (k <K>, v <V>) como seus argumentos de entrada) que é chamada sobre os elementos do mapa na ordem de chave classificada.

Portanto, uma versão do código na resposta postada por Mingu pode ser semelhante 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) })
}

A sortedMapIntString()função pode ser reutilizada para qualquer map[int]string(assumindo que a mesma ordem de classificação seja desejada), mantendo cada uso em apenas duas linhas de código.

As desvantagens incluem:

  • É mais difícil de ler para quem não está acostumado a usar funções de primeira classe
  • Pode ser mais lento (não fiz comparações de desempenho)

Outros idiomas têm várias soluções:

  • Se o uso de <K>e <V>(para denotar os tipos de chave e valor) parece um pouco familiar, esse modelo de código não é muito diferente dos modelos C ++.
  • Clojure e outras linguagens oferecem suporte a mapas classificados como tipos de dados fundamentais.
  • Embora eu não saiba de nenhuma maneira de Go fazer rangeum tipo de primeira classe que possa ser substituído por um personalizado ordered-range(no lugar de rangeno código original), acho que algumas outras linguagens fornecem iteradores que são poderosos o suficiente para realizar o mesmo coisa.
James Craig Burley
fonte
2
Vale a pena ressaltar, para iniciantes, que a sintaxe <K>, <V> não é compatível com Go.
justinhj de
2

Em resposta a James Craig Burley's resposta . Para fazer um design limpo e reutilizável, pode-se escolher uma abordagem mais orientada a objetos. Desta forma, os métodos podem ser associados com segurança aos tipos do mapa especificado. Para mim, essa abordagem parece mais limpa e organizada.

Exemplo:

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

Exemplo de playground estendido com vários tipos de mapa.

Nota importante

Em todos os casos, o mapa e a fatia classificada são desacoplados a partir do momento em que o forloop sobre o mapa rangeé concluído. O que significa que, se o mapa for modificado após a lógica de classificação, mas antes de usá-lo, você poderá ter problemas. (Não thread / Go rotina segura). Se houver uma alteração no acesso de gravação do mapa paralelo, você precisará usar um mutex em torno das gravações e do forloop classificado .

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

Isso fornece a você o exemplo de código no mapa de classificação. Basicamente, isso é o que eles fornecem:

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 isso é o que eu sugeriria usar em seu lugar :

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

O código completo pode ser encontrado neste Go Playground .

Erikas
fonte