Uzyskiwanie kawałka kluczy z mapy

230

Czy jest jakiś prostszy / ładniejszy sposób na zdobycie kawałka klucza z mapy w Go?

Obecnie iteruję po mapie i kopiuję klucze do wycinka:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Saswat Padhi
źródło
19
Prawidłowa odpowiedź brzmi „nie”, nie ma prostszego / ładniejszego sposobu.
dldnh

Odpowiedzi:

202

Na przykład,

package main

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

Aby być wydajnym w Go, ważne jest, aby zminimalizować przydziały pamięci.

peterSO
źródło
29
Nieco lepiej jest ustawić rzeczywisty rozmiar zamiast pojemności i całkowicie unikać dodawania. Zobacz moją odpowiedź, aby poznać szczegóły.
Vinay Pai,
3
Zauważ, że jeśli mymapnie jest zmienną lokalną (i dlatego podlega wzrostowi / zmniejszaniu), jest to jedyne właściwe rozwiązanie - zapewnia, że ​​jeśli rozmiar mymapzmian między inicjalizacją keysi forpętlą nie będzie żadnych problemy z ograniczeniami.
Melllvar,
12
mapy nie są bezpieczne przy jednoczesnym dostępie, żadne rozwiązanie nie jest dopuszczalne, jeśli inny goroutine może zmienić mapę.
Vinay Pai,
@VayayPai można czytać na mapie z wielu goroutinów, ale nie pisać
darethas
@darethas, co jest powszechnym nieporozumieniem. Detektor wyścigu oznaczy to użycie od 1.6. Z informacji o wydaniu: „Jak zawsze, jeśli jeden goroutine pisze na mapie, żaden inny goroutine nie powinien jednocześnie czytać ani pisać mapy. Jeśli środowisko wykonawcze wykryje ten warunek, drukuje diagnozę i powoduje awarię programu”. golang.org/doc/go1.6#runtime
Vinay Pai
375

To stare pytanie, ale oto moje dwa centy. Odpowiedź PeterSO jest nieco bardziej zwięzła, ale nieco mniej wydajna. Wiesz już, jak duży będzie, więc nie musisz nawet używać append:

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

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

W większości sytuacji prawdopodobnie nie będzie to miało większego znaczenia, ale nie będzie to dużo więcej pracy, aw moich testach (przy użyciu mapy z 1 000 000 losowych int64kluczy, a następnie wygenerowaniu tablicy kluczy dziesięć razy przy każdej metodzie) chodziło o 20% szybciej, aby przypisać członków tablicy bezpośrednio niż użyć append.

Chociaż ustawienie pojemności eliminuje realokacje, append wciąż musi wykonać dodatkową pracę, aby sprawdzić, czy osiągnięto pojemność na każdym append.

Vinay Pai
źródło
46
To wygląda dokładnie tak samo jak kod OP. Zgadzam się, że to lepszy sposób, ale jestem ciekawy, czy nie zauważyłem różnicy między kodem tej odpowiedzi a kodem OP.
Emmaly Wilson,
4
Dobrze, że jakoś spojrzałem na inne odpowiedzi i przegapiłem, że moja odpowiedź jest dokładnie taka sama jak OP. No cóż, przynajmniej wiemy teraz w przybliżeniu, jaka jest kara za niepotrzebne użycie append :)
Vinay Pai,
5
Dlaczego nie używasz indeksu z zakresu, for i, k := range mymap{. W ten sposób nie potrzebujesz i ++?
mvndaai
30
Może czegoś mi brakuje, ale jeśli tak i, k := range mymap, to ibędą klucze i kbędą wartości odpowiadające tym kluczom na mapie. To naprawdę nie pomoże ci zapełnić kawałka kluczy.
Vinay Pai
4
@Alaska, jeśli martwisz się kosztem przydzielenia jednej tymczasowej zmiennej licznika, ale uważasz, że wywołanie funkcji zajmie mniej pamięci, powinieneś nauczyć się, co tak naprawdę dzieje się po wywołaniu funkcji. Wskazówka: To nie jest magiczne zaklęcie, które robi rzeczy za darmo. Jeśli uważasz, że obecnie akceptowana odpowiedź jest bezpieczna przy jednoczesnym dostępie, musisz także wrócić do podstaw: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai,
79

Możesz także pobrać tablicę kluczy typu za []Valuepomocą metody MapKeysstruct Valuez pakietu „reflect”:

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]
}
Denis Kreshikhin
źródło
1
Myślę, że jest to dobre podejście, jeśli istnieje szansa na równoczesny dostęp do mapy: nie panikuje, jeśli mapa powiększy się podczas pętli. Jeśli chodzi o wydajność, nie jestem do końca pewny, ale podejrzewam, że przewyższa ona dołączone rozwiązanie.
Atila Romero,
@AtilaRomero Nie jestem pewien, czy to rozwiązanie ma jakieś zalety, ale gdy używa się refleksji do jakichkolwiek celów, jest to bardziej przydatne, ponieważ pozwala wziąć klucz jako wartość wpisaną bezpośrednio.
Denis Kreshikhin,
10
Czy istnieje sposób na konwersję tego []string?
Doron Behar
14

Lepszym sposobem na to byłoby użycie append:

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

Poza tym nie masz szczęścia - Go nie jest bardzo ekspresyjnym językiem.

po prawej stronie
źródło
10
Jest to jednak mniej wydajne niż oryginalne - append dokona wielu alokacji, aby powiększyć podstawową tablicę i musi aktualizować długość plasterka przy każdym wywołaniu. Powiedzenie keys = make([]int, 0, len(mymap))pozbędzie się alokacji, ale spodziewam się, że nadal będzie wolniejsze.
Nick Craig-Wood
1
Ta odpowiedź jest bezpieczniejsza niż użycie len (mymap), jeśli ktoś inny zmieni mapę podczas tworzenia kopii.
Atila Romero,
7

Zrobiłem szkicowy test porównawczy dla trzech metod opisanych w innych odpowiedziach.

Oczywiście wcześniejsze przydzielenie wycinka przed wyciągnięciem kluczy jest szybsze niż appending, ale, co zaskakujące, reflect.ValueOf(m).MapKeys()metoda jest znacznie wolniejsza niż ta ostatnia:

 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

Oto kod: https://play.golang.org/p/Z8O6a2jyfTH (uruchamianie go na boisku przerywa twierdzenie, że trwa to zbyt długo, więc cóż, uruchom go lokalnie.)

Nico Villanueva
źródło
2
W swojej keysAppendfunkcji możesz ustawić pojemność keystablicy make([]uint64, 0, len(m)), co drastycznie zmieniło dla mnie wydajność tej funkcji.
keithbhunter
1

Odwiedź 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)
}
Lalit Sharma
źródło