55

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?

sensorario
  • 20,262
  • 30
  • 97
  • 159

3 Answers3

114

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",
}
8

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

Community
  • 1
  • 1
icza
  • 389,944
  • 63
  • 907
  • 827
1

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

Peter Gloor
  • 917
  • 6
  • 19
  • Your `initMap` function should supply an initial capacity for the map as a second argument to `make`: `len(sa)`. – seh Dec 12 '16 at 01:11
  • 1
    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. – Peter Gloor Dec 12 '16 at 20:12
  • 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. – seh Dec 12 '16 at 21:53