Получение фрагмента ключей с карты

230

Есть ли какой-нибудь более простой / приятный способ получить часть ключей с карты в Go?

В настоящее время я перебираю карту и копирую ключи в срез:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Сасват Падхи
источник
19
Правильный ответ - нет, нет более простого / более хорошего способа.
dldnh

Ответы:

203

Например,

package main

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

Чтобы быть эффективным в Go, важно минимизировать распределение памяти.

peterSO
источник
29
Немного лучше установить фактический размер вместо емкости и избегать добавления в целом. Смотрите мой ответ для деталей.
Винай Пай
3
Обратите внимание, что если mymapне локальная переменная (и, следовательно, подвержена росту / уменьшению), это единственное правильное решение - оно гарантирует, что если размер mymapизменений между инициализацией keysи forциклом не будет, о проблемах.
Меллвар
12
карты не безопасны при одновременном доступе, и ни одно из решений не является приемлемым, если другая программа может изменить карту.
Винай Пай
@VinayPai можно читать с карты из нескольких программ, но не писать
см.
@darethas, это распространенное заблуждение. Детектор гонки помечает это использование с 1.6. Из примечаний к выпуску: «Как всегда, если одна программа записывает данные на карту, никакая другая программа не должна одновременно считывать или записывать карту. Если среда выполнения обнаруживает это условие, она печатает диагноз и вылетает из программы». golang.org/doc/go1.6#runtime
Винай Пай
374

Это старый вопрос, но вот мои два цента. Ответ PeterSO немного более краткий, но чуть менее эффективный. Вы уже знаете, насколько большим он будет, поэтому вам даже не нужно использовать append:

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

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

В большинстве ситуаций это, вероятно, не будет иметь большого значения, но это не намного больше работы, и в моих тестах (используя карту с 1 000 000 случайных int64ключей и затем генерируя массив ключей десять раз с каждым методом), это было примерно На 20% быстрее назначать члены массива напрямую, чем использовать append.

Несмотря на то, что установка емкости исключает перераспределение, добавлению все равно приходится выполнять дополнительную работу, чтобы проверить, достигли ли вы емкости для каждого добавления.

Винай Пай
источник
46
Это выглядит точно так же, как код ОП. Я согласен, что это лучший способ, но мне любопытно, если я пропустил разницу между кодом этого ответа и кодом OP.
Эммали Уилсон
4
Хороший вопрос, я как-то посмотрел на другие ответы и пропустил, что мой ответ точно такой же, как ОП. Ну что ж, по крайней мере, теперь мы примерно знаем, что такое штраф за ненужное добавление :)
Vinay Pai
5
Почему вы не используете индекс с диапазоном for i, k := range mymap{. Таким образом, вам не нужен i ++?
mvndaai
30
Может быть, я что-то здесь упускаю, но если вы это сделали i, k := range mymap, то iбудут ключи и kбудут значения, соответствующие этим ключам на карте. Это на самом деле не поможет вам заполнить кусок ключей.
Винай Пай
4
@ Аляска, если вас беспокоит стоимость выделения одной временной переменной-счетчика, но вы думаете, что вызов функции займет меньше памяти, вы должны узнать, что на самом деле происходит при вызове функции. Подсказка: это не волшебное заклинание, которое делает вещи бесплатно. Если вы считаете, что принятый в настоящее время ответ безопасен при одновременном доступе, вам также необходимо вернуться к основам: blog.golang.org/go-maps-in-action#TOC_6 .
Винай Пай
79

Вы также можете взять массив ключей с типом []Valueметодом MapKeysstruct Valueиз пакета «refle»:

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
Я думаю, что это хороший подход, если есть шанс одновременного доступа к карте: это не будет паниковать, если карта увеличивается во время цикла. Что касается производительности, я не совсем уверен, но я подозреваю, что она превосходит решение для добавления.
Атила Ромеро
@AtilaRomero Не уверен, что у этого решения есть какие-либо преимущества, но при использовании отражения для каких-либо целей это более полезно, поскольку оно позволяет получить ключ в качестве значения, введенного напрямую.
Денис Крешихин
10
Есть ли способ преобразовать это в []string?
Дорон Бехар
14

Лучший способ сделать это - использовать append:

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

Кроме того, вам не повезло - иди не очень выразительный язык.

rightfold
источник
10
Это менее эффективно, чем оригинал - append будет делать несколько выделений для увеличения базового массива и должен обновлять длину среза при каждом вызове. Скажем keys = make([]int, 0, len(mymap)), избавится от распределений, но я ожидаю, что все равно будет медленнее.
Ник Крейг-Вуд
1
Этот ответ безопаснее, чем использование len (mymap), если кто-то еще меняет карту во время создания копии.
Атила Ромеро
7

Я сделал схематичный тест по трем методам, описанным в других ответах.

Очевидно, что предварительное выделение фрагмента перед нажатием клавиш происходит быстрее, чем appending, но, что удивительно, reflect.ValueOf(m).MapKeys()метод значительно медленнее последнего:

 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

Вот код: https://play.golang.org/p/Z8O6a2jyfTH (запуск его на детской площадке прерывается, утверждая, что это занимает слишком много времени, поэтому, хорошо, запустите его локально.)

Нико Виллануева
источник
2
В вашей keysAppendфункции вы можете установить емкостьkeys массива make([]uint64, 0, len(m)), что радикально изменило производительность этой функции для меня.
Keithbhunter
1

Посетите 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)
}
Лалит Шарма
источник