В голанге есть хороший способ получить фрагмент значений с карты?


89

Если у меня есть карта m, есть ли лучший способ получить фрагмент значений 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)
 }

Есть ли у карты встроенная функция? Есть ли функция в пакете Go, или это лучший код, который мне нужно сделать?


1
вместо '_' в цикле for назовите его idx и откажитесь от бизнеса idx ++
Питер Агнью

Нет, он не может, когда вы перемещаетесь по карте, она возвращает ключ, значение, а не индекс, значение. В своем примере он использует 1 в качестве первого ключа, и это сделает индексы в срезе v некорректными, потому что начальный индекс будет равен 1, а не нулю, а когда он достигнет 4, он выйдет за пределы допустимого диапазона. play.golang.org/p/X8_SbgxK4VX
Popmedic

@Popmedic Вообще-то, да, он может. просто замените _на idxи используйте idx-1при присвоении значений срезов.
hewiefreeman

3
@ newplayer66, это очень опасный паттерн.
Popmedic

Ответы:


60

К сожалению нет. Для этого нет встроенного способа.

В качестве примечания, вы можете опустить аргумент емкости при создании среза:

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

Подразумевается, что емкость должна быть такой же, как и длина.


Думаю, есть способ получше: stackoverflow.com/a/61953291/1162217
Лукас Лукач

58

В дополнение к сообщению Джимта:

Вы также можете использовать appendвместо явного присвоения значений их индексам:

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

Обратите внимание, что длина равна нулю (элементов еще нет), но емкость (выделенное пространство) инициализируется количеством элементов m. Это сделано, поэтому appendне нужно выделять память каждый раз, когда vзаканчивается емкость среза .

Вы также можете makeвырезать срез без значения емкости и позволить appendвыделить память для себя.


Мне было интересно, будет ли это медленнее (при условии предварительного распределения)? Я провел грубый тест с map [int] int, и он оказался примерно на 1-2% медленнее. Есть идеи, стоит ли об этом беспокоиться или просто смириться с этим?
masebase

1
Я бы предположил, что добавление будет немного медленнее, но эта разница в большинстве случаев незначительна. Тест сравнения прямого присвоения и добавления .
nemo

1
Будьте осторожны, смешивая это с приведенным выше ответом - добавление к массиву после использования make([]appsv1.Deployment, len(d))будет добавлено к группе пустых элементов, которые были созданы, когда вы выделили len(d)пустые элементы.
Anirudh Ramanathan

1

Насколько мне известно, в go нет способа конкатенации строк / байтов в результирующую строку без создания как минимум / двух / копий.

В настоящее время вам необходимо увеличить байт [], так как все строковые значения являются константными, ЗАТЕМ вы должны использовать встроенную строку, чтобы язык создал «благословенный» строковый объект, для которого он скопирует буфер, поскольку где-то может быть ссылка на адрес, поддерживающий байт [].

Если подходит байт [], то вы можете получить очень небольшое преимущество по сравнению с функцией bytes.Join, сделав одно выделение и сделав копию, вызывающую себя.

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

Вы можете использовать этот mapsпакет:

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

Тогда все, что вам нужно позвонить, это

values := maps.GetValuesIntString(m)

Это типобезопасный для этой общей mapкомбинации. Вы можете использовать generateдругие типобезопасные функции для любого другого типа mapиспользованияmapper инструмента в том же пакете.

Полное раскрытие информации: я являюсь создателем этого пакета. Я создал его, потому что обнаружил, что mapнеоднократно переписывал эти функции .


1

Не обязательно лучше, но более чистый способ сделать это - определить ДЛИНУ и ЕМКОСТЬ среза, напримерtxs := 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)
    }

Полный пример:

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
}

Простое решение, но много ошибок. Прочтите этот пост в блоге, чтобы узнать больше: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas


Ответ не «неправильный». В вопросе используется индекс, а не добавление к фрагменту. Если вы используете добавление к срезу, тогда да, установка длины впереди будет плохой. Вот вопрос, так как он play.golang.org/p/nsIlIl24Irn Конечно, этот вопрос не идиоматический, и я все еще учился. Немного улучшенная версия, больше похожая на то, о чем вы говорите, - play.golang.org/p/4SKxC48wg2b
masebase

1
Привет, @masebase, я считаю это «неправильным», потому что в нем говорится: «К сожалению, нет. Встроенного способа сделать это нет», но есть «лучшее» решение, на которое мы оба указываем сейчас, 2 года спустя - с помощью добавления () и определяя как длину, так и емкость. Но я понимаю вашу точку зрения, что называть это «неправильным» тоже не совсем правильно. Я изменю свое первое предложение на: «Не обязательно лучше, но более чистый способ сделать это». Я поговорил с ~ 10 разработчиками, и все согласились, что append () - это более чистый способ преобразовать карту в фрагмент без использования вспомогательного индекса. Я узнал об этом также по пути публикации
Лукас Лукач
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.