Classificar os valores do mapa Go por chaves


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

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
}

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 .


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.

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

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 .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.