Initializing a Go map in a single statement

36,759

Solution 1

Yes, you can create a map with a single statement (called a composite literal in the spec):

var keys = map[int]string{
    1: "aa",
    2: "ab",
    3: "ac",
    4: "ba",
    5: "bb",
    6: "bc",
    7: "ca",
    8: "cb",
    9: "cc",
}

Or, if you are inside of a function, you can use a short variable declaration:

keys := map[int]string{
    1: "aa",
    2: "ab",
    3: "ac",
    4: "ba",
    5: "bb",
    6: "bc",
    7: "ca",
    8: "cb",
    9: "cc",
}

Solution 2

When there is logic between keys and values, you may also use a loop to initialize the map. "Put" the logic into the loop body. This may be significantly shorter than using a composite literal enumerating all key-value pairs, especially if the number of key-value pairs is big.

Your example can be implemented with this:

m := map[int]string{}
for i := 0; i < 9; i++ {
    m[i+1] = string("abc"[i/3]) + string("abc"[i%3])
}
fmt.Println(m)

Output (try it on the Go Playground):

map[5:bb 8:cb 4:ba 2:ab 3:ac 6:bc 7:ca 9:cc 1:aa]

A variant of this solution (using a different logic implementation):

m := map[int]string{}
for i := 0; i < 9; i++ {
    m[i+1] = "abc"[i/3:i/3+1] + "abc"[i%3:i%3+1]
}
fmt.Println(m)

Output is the "same". Try this variant on the Go Playground.

And even more solutions, now posting only the loop body (Playground links: another #1, another #2):

// Another #1:
m[i+1] = fmt.Sprintf("%c%c", "abc"[i/3], "abc"[i%3])
// Another #2:
m[i+1] = fmt.Sprintf("%c%c", 'a'+i/3, 'a'+i%3)

A different approach might use 2 loops (embedded) which generate the value, and calculates the key from the value:

for i := 'a'; i <= 'c'; i++ {
    for j := 'a'; j <= 'c'; j++ {
        m[int((i-'a')*3+j-'a'+1)] = string(i) + string(j)
    }
}

Try this on the Go Playground.

If the number of values is not big, another viable approach can be to enumerate all the elements in one string value, and use subslicing (which is efficient as no new backing arrays will be created, the backing array of the strings is shared):

const s = "aaabacbabbbccacbcc"

m := map[int]string{}
for i := 0; i < 9; i++ {
    m[i+1] = s[i*2 : i*2+2]
}
fmt.Println(m)

Output (try this on the Go Playground):

map[9:cc 1:aa 2:ab 5:bb 8:cb 3:ac 4:ba 6:bc 7:ca]

Also note that if the key is of type int and the set of keys is (more or less) contiguous, often it is more efficient (both memory and performance-wise) to use a slice instead:

m := make([]string, 10)
for i := 0; i < 9; i++ {
    m[i+1] = fmt.Sprintf("%c%c", 'a'+i/3, 'a'+i%3)
}
fmt.Printf("%q\n", m)

m2 := []string{"", "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"}
fmt.Printf("%q\n", m2)

m3 := []string{1: "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"}
fmt.Printf("%q\n", m3)

Output (try it on the Go Playground):

["" "aa" "ab" "ac" "ba" "bb" "bc" "ca" "cb" "cc"]
["" "aa" "ab" "ac" "ba" "bb" "bc" "ca" "cb" "cc"]
["" "aa" "ab" "ac" "ba" "bb" "bc" "ca" "cb" "cc"]

As you can see in the third example m3, you can use optional indices in the composite literal to specify the index of the value following. More about this here: Keyed items in golang array initialization

Solution 3

My preferred approach is a composite literal in a short variable declaration. In some cases a function might help to reduce clutter.

package main

import (
    "fmt"
)

// initMap initializes a map with an integer key starting at 1
func initMap(sa []string) map[int]string {
    m := make(map[int]string, len(sa))
    for k, v := range sa {
        m[k+1] = v // add 1 to k as it is starting at base 0
    }
    return m
}

// main is the entry point of any go application
func main() {
    // My preferred approach is a composite literal in a short variable declaration
    keys := map[int]string{1: "aa", 2: "ab", 3: "ac", 4: "ba", 5: "bb", 6: "bc", 7: "ca", 8: "cb", 9: "cc"}
    fmt.Println(keys)

    // Using a function to initialize the map might help to avoid clutter
    keys2 := initMap([]string{"aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"})
    fmt.Println(keys2)
}

See it in action at https://play.golang.org/p/Rrb9ChBkXW

Share:
36,759
sensorario
Author by

sensorario

The winter is coming.

Updated on July 09, 2022

Comments

  • sensorario
    sensorario almost 2 years

    This is my code:

    var keys map[int]string
    keys = make(map[int]string)
    
    keys[1] = "aa"
    keys[2] = "ab"
    keys[3] = "ac"
    keys[4] = "ba"
    keys[5] = "bb"
    keys[6] = "bc"
    keys[7] = "ca"
    keys[8] = "cb"
    keys[9] = "cc"
    

    Can I do the same thing in one statement and/or in one line?

  • subrat71
    subrat71 over 7 years
    Your initMap function should supply an initial capacity for the map as a second argument to make: len(sa).
  • Peter Gloor
    Peter Gloor over 7 years
    The capacity is important for slices, but maps work different and dont have a capacity. You are right, in case of large maps you can and should provide an optional size parameter in the make statement. In case of small maps, like in this example, you probably wont recognize any measurable performance gain.
  • subrat71
    subrat71 over 7 years
    Fine, call it "initial space," as the specification does. The point is that it avoids the need for subsequent allocation and reorganization of the map. It's the "capacity for avoiding table growth" given its current allocation.