In golang c'è un bel modo per ottenere una fetta di valori da una mappa?


90

Se ho una mappa m c'è un modo migliore per ottenere una fetta dei valori v allora

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

C'è una caratteristica costruita di una mappa? C'è una funzione in un pacchetto Go, o questo è il miglior codice da fare se devo?


1
invece di '_' nel tuo ciclo for, chiamalo idx e abbandona il business idx ++
Peter Agnew

No, non può, quando passi su una mappa restituisce chiave, valore non indice, valore. Nel suo esempio usa 1 come prima chiave e questo renderà gli indici nella slice v non corretti perché l'indice iniziale sarà 1 non zero e quando arriva a 4 sarà fuori intervallo. play.golang.org/p/X8_SbgxK4VX
Popmedic

@ Popmedic In realtà, sì, può. basta sostituire _con idxe utilizzare idx-1quando si assegnano i valori della fetta.
hewiefreeman

3
@ newplayer66, questo è uno schema molto pericoloso.
Popmedic

Risposte:


60

Sfortunatamente no. Non esiste un modo integrato per farlo.

Come nota a margine, puoi omettere l'argomento della capacità nella creazione della sezione:

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

La capacità è implicita essere la stessa della lunghezza qui.


1
Penso che ci sia un modo migliore: stackoverflow.com/a/61953291/1162217
Lukas Lukac

58

In aggiunta al post di jimt:

Puoi anche utilizzare appendanziché assegnare esplicitamente i valori ai loro indici:

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

Si noti che la lunghezza è zero (nessun elemento ancora presente) ma la capacità (spazio allocato) viene inizializzata con il numero di elementi di m. In questo modo appendnon è necessario allocare memoria ogni volta che si vesaurisce la capacità della slice .

Puoi anche makeeseguire la slice senza il valore della capacità e lasciare appendallocare la memoria per se stessa.


Mi chiedevo se questo sarebbe stato più lento (assumendo un'allocazione anticipata)? Ho fatto un benchmark grezzo con una mappa [int] int e sembrava circa 1-2% più lento. Qualche idea se questo è qualcosa di cui preoccuparsi o semplicemente andare con esso?
masebase

1
Presumo che append sia un po 'più lento, ma questa differenza è, nella maggior parte dei casi, trascurabile. Benchmark confrontando assegnazione diretta e append .
nemo

1
Fai attenzione a mescolare questo con la risposta sopra: aggiungendo all'array dopo l'uso make([]appsv1.Deployment, len(d))si aggiungerà a un gruppo di elementi vuoti che sono stati creati quando hai assegnato len(d)elementi vuoti.
Anirudh Ramanathan,

2

Non necessariamente migliore, ma il modo più pulito per farlo è definire sia la Slice LENGTH che la CAPACITY cometxs := 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)
    }

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

Soluzione semplice ma molti trucchi. Leggi questo post del blog per maggiori dettagli: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas


La risposta non è "sbagliata". La domanda utilizza un indice e nessuna aggiunta per la sezione. Se usi append sulla slice, sì, impostare la lunghezza in avanti sarebbe male. Ecco la domanda in quanto è play.golang.org/p/nsIlIl24Irn Ammesso che la domanda non sia idiomatica, vai e stavo ancora imparando. Una versione leggermente migliorata più simile a quella di cui parli è play.golang.org/p/4SKxC48wg2b
masebase

1
Ciao @masebase, lo considero "sbagliato" perché afferma: "Sfortunatamente no. Non esiste un modo integrato per farlo.", Ma c'è una soluzione "migliore" che entrambi stiamo segnalando ora 2 anni dopo - utilizzando l'append () e definendo sia la lunghezza che la capacità. Ma vedo che il tuo punto di vista è che definirlo "sbagliato" non è nemmeno esatto. Cambierò la mia prima frase in: "Non necessariamente migliore, ma il modo più pulito per farlo è". Ho parlato con ~ 10 sviluppatori e tutti hanno concordato che append () è un modo più pulito per convertire una mappa in uno slice senza utilizzare un indice di supporto. L'ho saputo anche durante la pubblicazione
Lukas Lukac

1

Per quanto ne so, go non ha un metodo per la concatenazione di stringhe / byte in una stringa risultante senza fare almeno / due / copie.

Al momento devi aumentare di un [] byte poiché tutti i valori di stringa sono const, POI devi usare la stringa incorporata per fare in modo che il linguaggio crei un oggetto stringa 'benedetto', per il quale copierà il buffer poiché qualcosa da qualche parte potrebbe avere un riferimento all'indirizzo che supporta il byte [].

Se un byte [] è adatto, puoi ottenere un leggero vantaggio sulla funzione bytes.Join effettuando un'allocazione e facendo la copia chiama te stesso.

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

1

Puoi usare questo mapspacchetto:

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

Allora tutto quello che devi chiamare è

values := maps.GetValuesIntString(m)

È indipendente dai tipi per quella mapcombinazione comune . È possibile generatealtre funzioni indipendenti dai tipi per qualsiasi altro tipo di maputilizzo dello mapperstrumento nello stesso pacchetto.

Divulgazione completa: sono il creatore di questo pacchetto. L'ho creato perché mi sono ritrovato a riscrivere queste funzioni mappiù volte.

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.