Czy w golang jest dobry sposób na uzyskanie wycinka wartości z mapy?

89

Jeśli mam mapę m, czy jest lepszy sposób na uzyskanie wycinka wartości v

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

Czy istnieje wbudowana funkcja mapy? Czy w pakiecie Go jest funkcja, czy jest to najlepszy kod do zrobienia, jeśli muszę?

masebase
źródło
1
zamiast „_” w pętli for, nazwij to idx i porzuć biznes idx ++
Peter Agnew
Nie, nie może, kiedy przesuniesz się po mapie, zwraca klucz, wartość nie indeks, wartość. W swoim przykładzie używa 1 jako pierwszego klucza, co spowoduje, że indeksy w wycinku v będą niepoprawne, ponieważ indeks początkowy będzie miał wartość 1, a nie zero, a gdy osiągnie 4, będzie poza zakresem. play.golang.org/p/X8_SbgxK4VX
Popmedic
@Popmedic Właściwie tak, może. wystarczy wymienić _ze idxi używać idx-1podczas przypisywania wartości plaster.
hewiefreeman
3
@ newplayer66, to bardzo niebezpieczny wzorzec.
Popmedic

Odpowiedzi:

60

Niestety nie. Nie ma na to wbudowanego sposobu.

Na marginesie, możesz pominąć argument pojemności podczas tworzenia wycinka:

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

Zakłada się, że pojemność jest taka sama jak długość tutaj.

jimt
źródło
Myślę, że jest lepszy sposób: stackoverflow.com/a/61953291/1162217
Lukas Lukac
58

Jako dodatek do posta jimta:

Możesz także użyć appendzamiast jawnie przypisywać wartości do ich indeksów:

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

Zauważ, że długość wynosi zero (brak jeszcze elementów), ale pojemność (przydzielone miejsce) jest inicjowana liczbą elementów m. Dzieje się tak, aby appendnie trzeba było przydzielać pamięci za każdym razem, gdy vskończy się pojemność wycinka .

Możesz także makewycinek bez wartości pojemności i pozwolić na appendalokację pamięci dla siebie.

nemo
źródło
Zastanawiałem się, czy to byłoby wolniejsze (zakładając alokację z góry)? Zrobiłem prymitywny benchmark z map [int] int i wydawał się około 1-2% wolniejszy. Jakieś pomysły, czy jest to coś, o co należy się martwić lub po prostu się z tym pogodzić?
masebase
1
Zakładałbym, że dodatek będzie nieco wolniejszy, ale ta różnica jest w większości przypadków nieistotna. Benchmark porównujący bezpośrednie przypisanie i dołączanie .
nemo
1
Uważaj, łącząc to z powyższą odpowiedzią - dołączenie do tablicy po użyciu make([]appsv1.Deployment, len(d))spowoduje dołączenie do kilku pustych elementów, które zostały utworzone podczas przydzielania len(d)pustych elementów.
Anirudh Ramanathan,
1

O ile obecnie wiem, go nie ma metody łączenia ciągów / bajtów w wynikowym ciągu bez tworzenia co najmniej / dwóch / kopii.

Obecnie musisz powiększyć bajt [], ponieważ wszystkie wartości łańcuchowe są stałymi, WTEDY musisz użyć wbudowanego ciągu znaków, aby język utworzył 'błogosławiony' obiekt łańcuchowy, do którego skopiuje bufor, ponieważ coś gdzieś może mieć odniesienie na adres z kopią bajtu [].

Jeśli bajt [] jest odpowiedni, możesz uzyskać niewielką przewagę nad bajtami. Dołącz do funkcji, dokonując jednej alokacji i wykonując kopiowanie wywołuje siebie.

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
źródło
1

Możesz użyć tego mapspakietu:

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

Wtedy wszystko, co musisz zadzwonić, to

values := maps.GetValuesIntString(m)

To jest bezpieczne dla tego typowego mappołączenia. W tym samym pakiecie można korzystać z generateinnych funkcji bezpiecznych dla typów dla dowolnego innego typu mapużycia mappernarzędzia.

Pełne ujawnienie: jestem twórcą tego pakietu. Stworzyłem go, ponieważ wielokrotnie przepisałem te funkcje map.

Chris Redford
źródło
1

Niekoniecznie lepiej, ale sposób czystszy to zrobić poprzez określenie zarówno DŁUGOŚĆ plasterek i pojemność jaktxs := 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)
    }

Pełny przykład:

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
}

Proste rozwiązanie, ale wiele problemów. Przeczytaj ten wpis na blogu, aby uzyskać więcej informacji: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Lukas Lukac
źródło
Odpowiedź nie jest „zła”. Pytanie używa indeksu i nie ma dołączania do wycinka. Jeśli użyjesz dołączania na plasterku, to tak, ustawienie długości z przodu byłoby złe. Oto pytanie, tak jak jest play.golang.org/p/nsIlIl24Irn Przyznaję, że to pytanie nie jest idiomatyczne i nadal się uczyłem. Nieco ulepszona wersja, bardziej podobna do tego, o czym mówisz, to play.golang.org/p/4SKxC48wg2b
masebase
1
Cześć @masebase, uważam to za „niewłaściwe”, ponieważ stwierdza: „Niestety, nie. Nie ma wbudowanego sposobu, aby to zrobić.”, Ale istnieje „lepsze” rozwiązanie, które oboje wskazujemy teraz 2 lata później - za pomocą dodatku () i określając zarówno długość, jak i pojemność. Ale widzę twój punkt widzenia, że ​​nazwanie tego „błędem” też nie jest trafne. Zmienię moje pierwsze zdanie na: „Niekoniecznie lepiej, ale czystszym sposobem na to jest”. Rozmawiałem z ~ 10 programistami i wszyscy zgodzili się, że append () to czystszy sposób na przekonwertowanie mapy na wycinek bez użycia indeksu pomocniczego. Dowiedziałem się tego również w drodze postu
Lukas Lukac