In Go how to get a slice of values from a map?

DictionaryGoSlice

Dictionary Problem Overview


If I have a map m is there a better way of getting a slice of the values v than this?

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

Is there a built-in feature of a map? Is there a function in a Go package, or is this the only way to do this?

Dictionary Solutions


Solution 1 - Dictionary

As an addition to jimt's post:

You may also use append rather than explicitly assigning the values to their indices:

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

Note that the length is zero (no elements present yet) but the capacity (allocated space) is initialized with the number of elements of m. This is done so append does not need to allocate memory each time the capacity of the slice v runs out.

You could also make the slice without the capacity value and let append allocate the memory for itself.

Solution 2 - Dictionary

Unfortunately, no. There is no builtin way to do this.

As a side note, you can omit the capacity argument in your slice creation:

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

The capacity is implied to be the same as the length here.

Solution 3 - Dictionary

Not necessarily better, but the cleaner way to do this is by defining both the Slice LENGTH and CAPACITY like 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)
	}

Full example:

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
}

Simple solution but a lot of gotchas. Read this blog post for more details: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Solution 4 - Dictionary

Go 1.18

You can use maps.Values from the golang.org/x/exp package.

> Values returns the values of the map m. The values will be in an indeterminate order.

func main() {
	m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"}
	v := maps.Values(m)
	fmt.Println(v) 
}

The package exp includes experimental code. The signatures may or may not change in the future, and may or may not be promoted to the standard library.

If you don't want to depend on an experimental package, you can implement a generic function yourself — or copy-paste the code from the exp package:

func Values[M ~map[K]V, K comparable, V any](m M) []V {
	r := make([]V, 0, len(m))
	for _, v := range m {
		r = append(r, v)
	}
	return r
}

Solution 5 - Dictionary

As far as I'm currently aware, go doesn't have a way method for concatenation of strings/bytes in to a resulting string without making at least /two/ copies.

You currently have to grow a []byte since all string values are const, THEN you have to use the string builtin to have the language create a 'blessed' string object, which it will copy the buffer for since something somewhere could have a reference to the address backing the []byte.

If a []byte is suitable then you can gain a very slight lead over the bytes.Join function by making one allocation and doing the copy calls your self.

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

Solution 6 - Dictionary

You can use this maps package:

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

Then all you have to call is

values := maps.GetValuesIntString(m)

It's type-safe for that common map combination. You can generate other type-safe functions for any other type of map using the mapper tool in the same package.

Full disclosure: I am the creator of this package. I created it because I found myself rewriting these functions for map repeatedly.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmasebaseView Question on Stackoverflow
Solution 1 - DictionarynemoView Answer on Stackoverflow
Solution 2 - DictionaryjimtView Answer on Stackoverflow
Solution 3 - DictionaryLukas LukacView Answer on Stackoverflow
Solution 4 - DictionaryblackgreenView Answer on Stackoverflow
Solution 5 - DictionaryMichael J. EvansView Answer on Stackoverflow
Solution 6 - DictionaryChris RedfordView Answer on Stackoverflow