Em golang, existe uma boa maneira de obter uma fatia de valores de um mapa?

90

Se eu tiver um mapa m, há uma maneira melhor de obter uma fatia dos valores v, então

package main
import (
  "fmt"
)

func main() {
    m := make(map[int]string)

    m[1] = "a"
    m[2] = "b"
    m[3] = "c"
    m[4] = "d"

    // Can this be done better?
    v := make([]string, len(m), len(m))
    idx := 0
    for  _, value := range m {
       v[idx] = value
       idx++
    }

    fmt.Println(v)
 }

Existe um recurso integrado de um mapa? Existe uma função em um pacote Go ou este é o melhor código a ser feito se for necessário?

masebase
fonte
1
em vez de '_' em seu loop for, chame-o de idx e descarte o negócio idx ++
Peter Agnew,
Não, ele não pode, quando você percorre um mapa, ele retorna uma chave, valor, não índice, valor. Em seu exemplo, ele usa 1 como a primeira chave e isso tornará os índices no segmento v incorretos porque o índice inicial será 1, não zero, e quando chegar a 4 estará fora do intervalo. play.golang.org/p/X8_SbgxK4VX
Popmedic de
@Popmedic Na verdade, ele pode. apenas substitua _por idxe use idx-1ao atribuir os valores da fatia.
hewiefreeman
3
@ newplayer66, esse é um padrão muito perigoso.
Popmedic

Respostas:

60

Infelizmente não. Não existe uma maneira embutida de fazer isso.

Como observação lateral, você pode omitir o argumento de capacidade na criação de sua fatia:

v := make([]string, len(m))

A capacidade está implícita como sendo o mesmo que o comprimento aqui.

Jimt
fonte
1
Acho que há uma maneira melhor: stackoverflow.com/a/61953291/1162217
Lukas Lukac
58

Como um complemento à postagem de jimt:

Você também pode usar, em appendvez de atribuir explicitamente os valores aos seus índices:

m := make(map[int]string)

m[1] = "a"
m[2] = "b"
m[3] = "c"
m[4] = "d"

v := make([]string, 0, len(m))

for  _, value := range m {
   v = append(v, value)
}

Observe que o comprimento é zero (nenhum elemento presente ainda), mas a capacidade (espaço alocado) é inicializada com o número de elementos de m. Isso é feito para appendque não seja necessário alocar memória cada vez que a capacidade da fatia vacabar.

Você também pode makedividir a fatia sem o valor da capacidade e deixar appendalocar a memória para si mesma.

nemo
fonte
Eu queria saber se isso seria mais lento (assumindo a alocação inicial)? Eu fiz um benchmark bruto com um map [int] int e pareceu cerca de 1-2% mais lento. Alguma ideia se isso é algo para se preocupar ou apenas ir em frente?
masebase
1
Suponho que append seja um pouco mais lento, mas essa diferença é, na maioria dos casos, insignificante. Benchmark comparando atribuição direta e anexo .
nemo
1
Tenha o cuidado de misturar isso com a resposta acima - anexar ao array depois de usar make([]appsv1.Deployment, len(d))irá anexar a um monte de elementos vazios que foram criados quando você alocou len(d)itens vazios.
Anirudh Ramanathan
2

Não necessariamente melhor, mas a maneira mais limpa de fazer isso é definir o LENGTH da fatia e a CAPACIDADE comotxs := make([]Tx, 0, len(txMap))

    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))

    for _, tx := range txMap {
        txs = append(txs, tx)
    }

Exemplo completo:

package main

import (
    "github.com/davecgh/go-spew/spew"
)

type Tx struct {
    from  string
    to    string
    value uint64
}

func main() {
    // Extra touch pre-defining the Map length to avoid reallocation
    txMap := make(map[string]Tx, 3)
    txMap["tx1"] = Tx{"andrej", "babayaga", 10}
    txMap["tx2"] = Tx{"andrej", "babayaga", 20}
    txMap["tx3"] = Tx{"andrej", "babayaga", 30}

    txSlice := getTXsAsSlice(txMap)
    spew.Dump(txSlice)
}

func getTXsAsSlice(txMap map[string]Tx) []Tx {
    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))
    for _, tx := range txMap {
        txs = append(txs, tx)
    }

    return txs
}

Solução simples, mas com muitos truques. Leia esta postagem do blog para mais detalhes: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Lukas Lukac
fonte
A resposta não é "errada". A pergunta usa um índice e nenhum anexo para a fatia. Se você usar append na fatia, sim, definir o comprimento na frente seria ruim. Aqui está a pergunta, pois é play.golang.org/p/nsIlIl24Irn Admitido que a pergunta não seja idiomática, vá e eu ainda estava aprendendo. Uma versão ligeiramente melhorada mais parecida com a que você está falando é play.golang.org/p/4SKxC48wg2b
masebase
1
Olá @masebase, considero "errado" porque afirma: "Infelizmente, não. Não existe uma forma incorporada de fazer isto.", Mas existe uma solução "melhor" que ambos apontamos agora 2 anos depois - utilizando o anexo () e definindo o comprimento e a capacidade. Mas eu entendo seu ponto de vista de que chamar isso de "errado" também não é correto. Vou mudar minha primeira frase para: "Não necessariamente melhor, mas a maneira mais limpa de fazer isso é". Falei com cerca de 10 desenvolvedores e todos concordaram que append () é uma maneira mais limpa de converter um mapa em uma fatia sem usar um índice auxiliar. Aprendi isso também na forma de postagem
Lukas Lukac
1

Até onde eu estou ciente, go não tem um método para concatenação de strings / bytes em uma string resultante sem fazer pelo menos / duas / cópias.

Você atualmente tem que aumentar um byte [] já que todos os valores de string são constantes, ENTÃO você tem que usar a string embutida para que a linguagem crie um objeto string 'abençoado', para o qual irá copiar o buffer, já que algo em algum lugar pode ter uma referência para o endereço que suporta o byte [].

Se um byte [] for adequado, você poderá obter uma ligeira vantagem sobre os bytes. Função de junção fazendo uma alocação e fazendo as chamadas de cópia você mesmo.

package main
import (
  "fmt"
)

func main() {
m := make(map[int]string)

m[1] = "a" ;    m[2] = "b" ;     m[3] = "c" ;    m[4] = "d"

ip := 0

/* If the elements of m are not all of fixed length you must use a method like this;
 * in that case also consider:
 * bytes.Join() and/or
 * strings.Join()
 * They are likely preferable for maintainability over small performance change.

for _, v := range m {
    ip += len(v)
}
*/

ip = len(m) * 1 // length of elements in m
r := make([]byte, ip, ip)
ip = 0
for  _, v := range m {
   ip += copy(r[ip:], v)
}

// r (return value) is currently a []byte, it mostly differs from 'string'
// in that it can be grown and has a different default fmt method.

fmt.Printf("%s\n", r)
}
Michael J. Evans
fonte
1

Você pode usar este mapspacote:

go get https://github.com/drgrib/maps

Então tudo que você tem que ligar é

values := maps.GetValuesIntString(m)

É seguro para essa mapcombinação comum . Você pode generateoutras funções de segurança de tipo para qualquer outro tipo de mapuso da mapperferramenta no mesmo pacote.

Divulgação completa: eu sou o criador deste pacote. Eu o criei porque me descobri reescrevendo essas funções maprepetidamente.

Chris Redford
fonte