Obtendo uma fatia de chaves de um mapa


230

Existe alguma maneira mais simples / agradável de obter uma fatia de chaves de um mapa no Go?

Atualmente, eu estou iterando sobre o mapa e copiando as chaves para uma fatia:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

19
A resposta correta é não, não há uma maneira mais simples / agradável.
Ddnh 16/0318

Respostas:


202

Por exemplo,

package main

func main() {
    mymap := make(map[int]string)
    keys := make([]int, 0, len(mymap))
    for k := range mymap {
        keys = append(keys, k)
    }
}

Para ser eficiente no Go, é importante minimizar as alocações de memória.


29
É um pouco melhor definir o tamanho real em vez da capacidade e evitar anexar completamente. Veja minha resposta para detalhes.
Vinay Pai

3
Observe que, se mymapnão for uma variável local (e, portanto, estiver sujeita a aumentar / diminuir), esta é a única solução adequada - ela garante que, se o tamanho das mymapalterações mudar entre a inicialização keyse o forloop, não haverá nenhuma saída. questões de limites.
Melllvar

12
os mapas não são seguros com acesso simultâneo, nem a solução é aceitável se outra goroutine puder alterar o mapa.
Vinay Pai 26/07

@VinayPai é bom para ler a partir de um mapa a partir de múltiplas goroutines, mas não escrever
darethas

@arethas é um equívoco comum. O detector de corrida sinalizará esse uso desde 1.6. Nas notas de versão: "Como sempre, se uma goroutine está gravando em um mapa, nenhuma outra goroutine deve estar lendo ou escrevendo o mapa simultaneamente. Se o tempo de execução detectar essa condição, ele imprimirá um diagnóstico e travará o programa". golang.org/doc/go1.6#runtime
Vinay Pai

375

Esta é uma pergunta antiga, mas aqui estão meus dois centavos. A resposta do PeterSO é um pouco mais concisa, mas um pouco menos eficiente. Você já sabe o quão grande será, então nem precisa usar o apêndice:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

Na maioria das situações, provavelmente não fará muita diferença, mas não é muito mais trabalhoso. Nos meus testes (usando um mapa com 1.000.000 de int64chaves aleatórias e gerando a matriz de chaves dez vezes com cada método), tratava-se de 20% mais rápido para atribuir membros da matriz diretamente do que usar anexar.

Embora a configuração da capacidade elimine realocações, o anexo ainda precisa fazer um trabalho extra para verificar se você atingiu a capacidade em cada anexo.


46
Parece exatamente o mesmo que o código do OP. Concordo que esse é o melhor caminho, mas estou curioso para saber se perdi a diferença entre o código desta resposta e o código do OP.
Emmaly Wilson

4
Bom ponto, de alguma forma, olhei para as outras respostas e perdi que minha resposta é exatamente a mesma do OP. Oh bem, pelo menos agora sabemos aproximadamente qual é a penalidade para o uso de acréscimo desnecessariamente :)
Vinay Pai

5
Por que você não está usando um índice com o intervalo for i, k := range mymap{,. Dessa forma, você não precisa do i ++?
Mvndaai

30
Talvez esteja faltando alguma coisa aqui, mas se você tiver i, k := range mymap, iserão chaves e kvalores correspondentes a essas chaves no mapa. Na verdade, isso não ajudará a preencher uma fatia de chaves.
Vinay Pai 2/16

4
@ Alaska se você estiver preocupado com o custo de alocação de uma variável temporária do contador, mas acha que uma chamada de função precisará de menos memória, você deve se informar sobre o que realmente acontece quando uma função é chamada. Dica: não é um encantamento mágico que faz as coisas de graça. Se você acha que a resposta atualmente aceita é segura com acesso simultâneo, também precisa voltar ao básico: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai

79

Você também pode pegar uma matriz de chaves com o tipo []Valuepelo método MapKeysde struct Valuedo pacote "refletir":

package main

import (
    "fmt"
    "reflect"
)

func main() {
    abc := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }

    keys := reflect.ValueOf(abc).MapKeys()

    fmt.Println(keys) // [a b c]
}

1
Eu acho que essa é uma boa abordagem se houver uma chance de acesso simultâneo ao mapa: isso não entrará em pânico se o mapa crescer durante o loop. Sobre o desempenho, não tenho muita certeza, mas suspeito que supera a solução anexada.
Atila Romero

@AtilaRomero Não tenho certeza de que esta solução tenha vantagens, mas quando usar a reflexão para qualquer finalidade, isso é mais útil, pois permite pegar uma chave conforme o Valor digitado diretamente.
Denis Kreshikhin 17/08/19

10
Existe uma maneira de converter isso para []string?
Doron Behar

14

Uma maneira melhor de fazer isso seria usar append:

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}

Fora isso, você está sem sorte - o Go não é uma linguagem muito expressiva.


10
É menos eficiente que o original - o append fará várias alocações para aumentar a matriz subjacente e precisa atualizar o tamanho da fatia a cada chamada. Dizer keys = make([]int, 0, len(mymap))se livrará das alocações, mas espero que ainda seja mais lento.
Nick Craig-Wood

1
Essa resposta é mais segura do que usar len (mymap), se outra pessoa alterar o mapa enquanto a cópia estiver sendo feita.
Atila Romero

7

Fiz uma referência superficial dos três métodos descritos em outras respostas.

Obviamente, pré-alocar a fatia antes de puxar as teclas é mais rápido que o appending, mas, surpreendentemente, o reflect.ValueOf(m).MapKeys()método é significativamente mais lento que o último:

 go run scratch.go
populating
filling 100000000 slots
done in 56.630774791s
running prealloc
took: 9.989049786s
running append
took: 18.948676741s
running reflect
took: 25.50070649s

Aqui está o código: https://play.golang.org/p/Z8O6a2jyfTH (executá-lo no playground aborta, alegando que leva muito tempo, então, bem, execute-o localmente).


2
Em sua keysAppendfunção, você pode definir a capacidade da keysmatriz com make([]uint64, 0, len(m)), o que mudou drasticamente o desempenho dessa função para mim.
Keithbhunter

1

Visite https://play.golang.org/p/dx6PTtuBXQW

package main

import (
    "fmt"
    "sort"
)

func main() {
    mapEg := map[string]string{"c":"a","a":"c","b":"b"}
    keys := make([]string, 0, len(mapEg))
    for k := range mapEg {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    fmt.Println(keys)
}
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.