Отримання фрагмента ключів від карти


230

Чи є якийсь простіший / приємніший спосіб отримати фрагмент ключів від карти в Go?

Наразі я повторюю карту і копіюю ключі на фрагмент:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

19
Правильна відповідь - ні, немає більш простого / приємного способу.
dldnh

Відповіді:


202

Наприклад,

package main

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

Для ефективності роботи в програмі Go важливо мінімізувати розподіл пам'яті.


29
Трохи краще встановити фактичний розмір замість ємності і уникати додавання взагалі. Детальну інформацію див. У моїй відповіді.
Vinay Pai

3
Зауважте, що якщо mymapце не локальна змінна (і тому вона підлягає зростанню / зменшенню), це єдине правильне рішення - це гарантує, що якщо розмір mymapзмін між ініціалізацією keysта forциклом не буде, позамежні питання.
Melllvar

12
Карти не є безпечними при одночасному доступі, і жодне рішення не є прийнятним, якщо інша goroutter може змінити карту.
Vinay Pai

@VinayPai це добре читати з карти з декількох гороутин, але не писати
darethas

@darethas - це поширена помилка. Детектор гонок позначить це використання з 1.6. З випуску зазначається: "Як завжди, якщо одна goutut пише на карту, жодна інша програма не повинна одночасно читати або писати карту. Якщо під час виконання програми виявляється ця умова, вона друкує діагноз і завершує роботу програми." golang.org/doc/go1.6#runtime
Vinay Pai

375

Це старе питання, але ось два мої центи. Відповідь PeterSO трохи більш стисла, але трохи менш ефективна. Ви вже знаєте, наскільки це буде великим, тому вам навіть не потрібно використовувати додаток:

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

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

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

Хоча встановлення ємності виключає перерозподіл, додаток все одно повинен виконати додаткову роботу, щоб перевірити, чи ви досягли ємності для кожного додатка.


46
Це виглядає точно так само, як і код OP. Я погоджуюся, що це кращий спосіб, але мені цікаво, якщо я пропустив різницю між кодом цієї відповіді та кодом ОП.
Еммалі Вілсон

4
Добре, я якось переглянув інші відповіді і пропустив, що моя відповідь точно така ж, як і в ОП. Ну добре, принаймні, тепер ми знаємо приблизно, що таке покарання за використання додавання надмірно :)
Vinay Pai

5
Чому ви не використовуєте індекс з діапазоном, for i, k := range mymap{. Таким чином, вам не потрібен i ++?
mvndaai

30
Можливо, мені щось тут не вистачає, але якщо ви це зробили i, k := range mymap, то iбудуть ключі і kбудуть значення, відповідні цим ключам на карті. Це насправді не допоможе вам заповнити шматок ключів.
Vinay Pai

4
@ Аляска, якщо ви стурбовані витратою на виділення однієї тимчасової змінної лічильника, але думаєте, що виклик функції займе менше пам'яті, ви повинні навчитись тому, що відбувається насправді, коли функція викликається. Підказка: Це не чарівний заклик, який робить речі безкоштовно. Якщо ви вважаєте, що прийнята на даний момент відповідь є безпечною при одночасному доступі, вам також потрібно повернутися до основ: blog.golang.org/go-maps-in-action#TOC_6 .
Вінай Пай

79

Ви також можете взяти масив клавіш з типом []Valueметодом MapKeysструктура Valueз пакету "відобразити":

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 Не впевнений, що це рішення має будь-які переваги, але коли використовувати рефлексію для будь-яких цілей, це корисніше, оскільки дозволяє взяти ключ як Value, набраний безпосередньо.
Денис Крешихін

10
Чи є спосіб це перетворити []string?
Дорон Бехар

14

Приємнішим способом зробити це було б append:

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

Крім цього, вам не пощастило - Go - це не дуже виразна мова.


10
Він менш ефективний, ніж оригінал - додавання зробить декілька виділень для вирощування базового масиву і має оновлювати довжину фрагмента кожного виклику. Казання keys = make([]int, 0, len(mymap))позбудеться асигнувань, але я очікую, що це все-таки буде повільніше.
Нік Крейг-Вуд

1
Ця відповідь безпечніша, ніж використання len (mymap), якщо хтось інший змінить карту під час створення копії.
Атіла Ромеро

7

Я зробив схематичний орієнтир щодо трьох методів, описаних в інших відповідях.

Очевидно, що попередньо розподілити фрагмент перед перетягуванням клавіш швидше, ніж append, але дивно, що 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.