105
votes

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

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 feature of a map? Is there a function in a Go package, or is this the best code to do if I have to?

5
instead of '_' in your for loop, call it idx and ditch the idx++ businessPeter Agnew
No, he can not, when you range over a map it returns key, value not index, value. In his example he uses 1 as the first key and that will make the indexes in the slice v incorrect because the start index will be 1 not zero, and when it gets to 4 it will be out of range. play.golang.org/p/X8_SbgxK4VXPopmedic
@Popmedic Actually, yes he can. just replace _ with idx and use idx-1 when assigning the slice values.hewiefreeman
@newplayer66, that is a very dangerous pattern.Popmedic

5 Answers

66
votes

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.

67
votes

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.

6
votes

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

1
votes

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

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.