154

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?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
masebase
  • 4,915
  • 3
  • 24
  • 20

5 Answers5

91

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.

nemo
  • 55,207
  • 13
  • 135
  • 135
  • I was wondering if this would be any slower (assuming up front allocation)? I did a crude benchmark with a map[int]int and it seemed about 1-2% slower. Any ideas if this is something to worry about or just go with it? – masebase Nov 17 '12 at 17:17
  • 1
    I would assume append to be a little bit slower but that difference is, in most cases, negligible. [Benchmark comparing direct assignment and append](http://pastie.org/5393131). – nemo Nov 17 '12 at 18:12
  • 3
    Be careful mixing this with the answer above - appending to the array after you use `make([]appsv1.Deployment, len(d))` will append to a bunch of empty elements that were created when you allocated `len(d)` empty items. – Anirudh Ramanathan Dec 11 '19 at 00:57
  • @AnirudhRamanathan - which "answer above"? (Answers get re-ordered!) – Jeff Learman May 09 '23 at 12:18
78

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.

jimt
  • 25,324
  • 8
  • 70
  • 60
  • 1
    I think there is a better way: https://stackoverflow.com/a/61953291/1162217 – Lukas Lukac May 22 '20 at 11:00
  • removing capacity, even leads to more problems than it solves and even is discouraged by some linters. In this way you need to use old C style index. So https://stackoverflow.com/a/13427931/3172371 "hits" the target. – Mihail Jul 25 '22 at 18:15
48

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 easily implement it yourself. In fact, the following code snippet is a copy-paste from the exp/maps package, originally authored by Ian Lance Taylor:

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
}
blackgreen
  • 34,072
  • 23
  • 111
  • 129
10

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

bruceg
  • 2,433
  • 1
  • 22
  • 29
Lukas Lukac
  • 7,766
  • 10
  • 65
  • 75
  • The answer is not "wrong". The question uses an index and no append for the slice. If you use append on the slice then yes setting the length up front would be bad. Here is the question as it is https://play.golang.org/p/nsIlIl24Irn Granted that question is not idiomatic go and I was still learning. A slightly improved version more like what you talking about is https://play.golang.org/p/4SKxC48wg2b – masebase May 28 '20 at 18:11
  • 1
    Hi @masebase, I consider it "wrong" because it states: "Unfortunately, no. There is no builtin way to do this.", but there is "better" solution we are both pointing out now 2 years later - using the append() and defining both the length and the capacity. But I do see your point that calling it "wrong" is not accurate neither. I will change my first sentence to: "Not necessarily better, but the cleaner way to do this is". I have talked to ~10 devs and everyone agreed the append() is a cleaner way to convert a map to a slice without using a helper index. I learned this also on the way of posting – Lukas Lukac May 29 '20 at 21:20
1

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