У golang чи є хороший спосіб отримати фрагмент значень з карти?


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 ++
Peter Agnew

Ні, він не може, коли ви перебираєте карту, вона повертає ключ, значення не індекс, значення. У своєму прикладі він використовує 1 як перший ключ, і це зробить індекси в зрізі v неправильними, оскільки початковий індекс буде 1 не нульовим, а коли він дійде до 4, він буде поза діапазоном. play.golang.org/p/X8_SbgxK4VX
Попмедік

@Popmedic Насправді, так, він може. просто замініть _на idxта використовуйте idx-1при призначенні значень зрізу.
hewiefreeman

3
@ newplayer66, це дуже небезпечний шаблон.
Попмедік

Відповіді:


60

На жаль, немає. Для цього немає вбудованого способу.

Як допоміжну примітку, ви можете опустити аргумент ємності у створенні вашого фрагмента:

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

Ємність передбачається такою ж, як тут.


Я думаю, що є кращий спосіб: stackoverflow.com/a/61953291/1162217
Лукас Лукач

58

Як доповнення до допису Jimt:

Ви також можете використовувати, 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виділити пам'ять для себе.


Мені було цікаво, чи це буде повільніше (припускаючи попереднє розподіл)? Я зробив сирий бенчмарк із картою [int] int, і це здавалося приблизно на 1-2% повільніше. Будь-які ідеї, якщо це щось, про що слід турбуватися, або просто піти на це?
masebase

1
Я вважаю, що додаток трохи повільніший, але ця різниця, в більшості випадків, незначна. Контрольний показник, що порівнює пряме призначення та додавання .
nemo

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

1

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

Наразі вам потрібно виростити байт [], оскільки всі значення рядків є const, ПОТІМ вам потрібно використовувати вбудований рядок, щоб мова створила «благословенний» рядовий об’єкт, для якого він скопіює буфер, оскільки щось десь може мати посилання на адресу, що підтримує байт [].

Якщо підходить байт [], ви можете отримати дуже незначне перевагу над байтами. Приєднайтеся до функції, зробивши одне виділення та зробивши копію, що викликає вас самого.

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.