Golang How to check if key exists in map

eye-catch Golang
Sponsored links

How to initialize map

There are two ways to initialize map in Golang.

Initialization without any key-value

If it’s not necessary to assign key-value pairs at the declaration time, we can initialize it in the following way.

map1 := make(map[int]string)
map1[1] = "one"
map1[7] = "seven"
map1[3] = "three"
map1[4] = "four"
map1[6] = "six"

Use make(map[<data_type>]<data_type>) if key-value pairs will be set later.

Initialization with key-value pairs

If key-value pairs need to be assigned, they can be directly set in the initialization. It looks similar to initializing Array/Slice.

map2 := map[string]int{
    "first":  1,
    "second": 2,
    "third":  3,
}
Sponsored links

How to get a value from map

After initializing a map, we need to read the data. The data can be read in a similar way to Array/Slice.

fmt.Println(map1[1])       // one
fmt.Println(map1[3])       // three
fmt.Println(map2["first"]) // 1
fmt.Println(map2["third"]) // 3

Just pass the key to the map. Then, the data can be read if it exists.

What if the key doesn’t exist in the map? The default value is read in this case.

fmt.Println(map1[99])        //
fmt.Println(map2["unknown"]) // 0

If the data type of the value is string, it’s empty. If it’s int, it’s 0.

Get the default value

Let’s check the default value depending on the data type if key doesn’t exist in the map.

type mapKey struct {
    key1 string
    key2 int
}

m1 := map[int]string{}
m1_2 := map[int]*string{}

m2 := map[int]int{}
m2_2 := map[int]*int{}

m3 := map[int]float64{}
m3_2 := map[int]*float64{}

m4 := map[int]time.Time{}
m4_2 := map[int]*time.Time{}

m5 := map[int]mapKey{}
m5_2 := map[int]*mapKey{}

fmt.Println(m1[1])   //
fmt.Println(m2[1])   // 0
fmt.Println(m3[1])   // 0
fmt.Println(m4[1])   // 0001-01-01 00:00:00 +0000 UTC
fmt.Println(m5[1])   // { 0}

fmt.Println(m1_2[1]) // <nil>
fmt.Println(m2_2[1]) // <nil>
fmt.Println(m3_2[1]) // <nil>
fmt.Println(m4_2[1]) // <nil>
fmt.Println(m5_2[1]) // <nil>

The default value is nil if the value data type is a pointer. If it’s a struct, the default value is assigned to the properties in the struct.

Check if key exists in map

It’s ok to use the default value if the application requires it but we often have to differentiate the following two cases.

  • The default value for a non-existing key
  • The same value as the default value is set to an existing key

Then, we have to check if the key exists in the map. This can easily be checked. When accessing the map with a key, it returns 2 values. The second value shows whether the key exists or not.

func showValue[K comparable, V int | string](m map[K]V, key K) {
    v, prs := m[key]
    if !prs {
        fmt.Printf("key does not exist [%+v] \n", key)
    } else {
        fmt.Printf("value: [%v] \n", v)
    }
}

prs is of type boolean. The key exists in the map if it’s true.

showValue(map1, 1)       // value: [one]
showValue(map1, 2)       // key does not exist [2]
showValue(map2, "first") // value: [1]
showValue(map2, "four")  // key does not exist [four]

range map doesn’t iterate the items in the same order

A map can be used with range keyword to iterate over the items in for-loop. However, the order is not guaranteed.

func iterate(m map[int]string) {
    for key, val := range m {
        fmt.Printf("key: %d, value: %s\n", key, val)
    }
    fmt.Println()
}

iterate(map1)
// key: 6, value: six
// key: 1, value: one
// key: 3, value: three
// key: 4, value: four
iterate(map1)
// key: 3, value: three
// key: 4, value: four
// key: 6, value: six
// key: 1, value: one

The order of the result is different for each call. We have to sort the map first if we need to iterate the items in a specific order.

Iterate the items in sorted order

As I mentioned, the map needs to be sorted first. However, if the map is sorted, range map doesn’t iterate the items in the order.

We need the sorted key instead. Then, use the key list in the for-loop.

func sortAndIterate(m map[int]string) {
    keys := make([]int, 0, len(m))

    for key := range m {
        keys = append(keys, key)
    }

    sort.Ints(keys)

    for _, key := range keys {
        fmt.Printf("key: %d, value: %s\n", key, m[key])
    }
}

make([]int, 0, len(m)) initializes a Slice with a fixed length. It makes the program a bit faster because the needed memory can be allocated in advance.

The map can be iterated over in sorted order.

sortAndIterate(map1)
// key: 1, value: one
// key: 3, value: three
// key: 4, value: four
// key: 6, value: six
// key: 7, value: seven

Use Struct as keys

Not only literals but also struct can be used as keys. Let’s define the following struct for example.

type mapKey struct {
    key1 string
    key2 int
}

The map can be initialized in the same way. Just specify the struct in the key.

mapWithStruct := make(map[mapKey]string)

Then, what we need to do is the same as before. Initialize the struct and use it.

mapWithStruct := make(map[mapKey]string)
mapKey1 := &mapKey{key1: "First", key2: 1}
mapKey2 := &mapKey{key1: "Second", key2: 2}
mapKey3 := &mapKey{key1: "Third", key2: 3}
mapWithStruct[*mapKey1] = "Value-1"
mapWithStruct[*mapKey2] = "Value-2"
mapWithStruct[*mapKey3] = "Value-3"

Okay, the map is ready to be read. The struct is used as keys and the value is set correctly.

Let’s read the value.

mapKey1_2 := &mapKey{key1: "First", key2: 1}
fmt.Println(mapKey1 == mapKey1_2)                          // false
fmt.Println(*mapKey1 == *mapKey1_2)                        // true

showValue(mapWithStruct, *mapKey1)                         // value: [Value-1]
showValue(mapWithStruct, mapKey{key1: "First", key2: 1})   // value: [Value-1]
showValue(mapWithStruct, mapKey{key1: "unknown", key2: 1}) // key does not exist [{key1:unknown key2:1}]

I defined another map that contains the same values. Comparing the two addresses returns of course false but the value comparison returns true. It means that the value can be read without having the same object.

The behavior is the same even if a pointer is used as keys.

mapWithStruct2 := make(map[*mapKey]string)
mapWithStruct2[mapKey1] = "Value-1"
mapWithStruct2[mapKey2] = "Value-2"
mapWithStruct2[mapKey3] = "Value-3"

showValue(mapWithStruct, *mapKey1)                       // value: [Value-1]
showValue(mapWithStruct, mapKey{key1: "First", key2: 1}) // value: [Value-1]

_, prs := mapWithStruct2[mapKey1]
fmt.Println(prs) // true

_, prs = mapWithStruct2[&mapKey{key1: "unknown", key2: 1}]
fmt.Println(prs) // false

Map of maps / nested map

If we need a map as values, we can of course create map of map. Let’s check how to initialize map again. It’s make(map[<data_type>]<data_type>. Since the second <data_type> becomes map, the code looks as follows to initialize map of map.

make(map[string]map[string]int)

Way 1 to set value

Then, set the value one by one.

mapOfMap := make(map[string]map[string]int)
nestedMapA := make(map[string]int)
nestedMapA["aa"] = 11
nestedMapA["ab"] = 12
nestedMapA["ac"] = 13
nestedMapB := make(map[string]int)
nestedMapB["ba"] = 21
nestedMapB["bb"] = 22
nestedMapB["bc"] = 23
mapOfMap["a"] = nestedMapA
mapOfMap["b"] = nestedMapB

fmt.Println(mapOfMap) // map[a:map[aa:11 ab:12 ac:13] b:map[ba:21 bb:22 bc:23]]

Each value has to be initialized because the value is map.

Way 2 to set value

In the previous code, the two variables need to be defined and they must be assigned at the end. If you don’t like the style, we can write it in the following way.

mapOfMap2 := make(map[string]map[string]int)
mapOfMap2["a"] = make(map[string]int)
mapOfMap2["a"]["aa"] = 11
mapOfMap2["a"]["ab"] = 12
mapOfMap2["a"]["ac"] = 13

mapOfMap2["b"] = make(map[string]int)
mapOfMap2["b"]["ba"] = 21
mapOfMap2["b"]["bb"] = 22
mapOfMap2["b"]["bc"] = 23

fmt.Println(mapOfMap2) // map[a:map[aa:11 ab:12 ac:13] b:map[ba:21 bb:22 bc:23]]

Assign the initialized map to the map value directly. We can remove the two variables in this way.

Way 3 to set all values at once

All values might be able to be set at once. We can write it in the following way in this case.

mapOfMap3 := map[string]map[string]int{
    "a": {
        "aa": 11,
        "ab": 12,
        "ac": 13,
    },
    "b": {
        "ba": 21,
        "bb": 22,
        "bc": 23,
    },
}

fmt.Println(mapOfMap3) // map[a:map[aa:11 ab:12 ac:13] b:map[ba:21 bb:22 bc:23]]

It looks like a JSON and is easy to read.

How fast if the map size is specified

make function accepts a second parameter for map to specify the initial size. The program becomes faster if necessary memory is allocated at once. If the space is not enough, additional memory needs to be allocated for each value set.

How fast is it actually? Let’s define the following two functions to compare them. It set a million key-value pairs.

const million = 1000000

func measureInt(m map[int]int) {
    start := time.Now()
    for i := 0; i < million; i++ {
        m[i] = i
    }
    elapsed := time.Since(start)
    fmt.Println(elapsed)
}

func measureString(m map[int]string) {
    start := time.Now()
    for i := 0; i < million; i++ {
        m[i] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    }
    elapsed := time.Since(start)
    fmt.Println(elapsed)
}

Then, call the function with map.

measureInt(make(map[int]int, million))       // 130.739575ms
measureInt(make(map[int]int))                // 282.562644ms
measureString(make(map[int]string, million)) // 167.671049ms
measureString(make(map[int]string))          // 303.875606ms

As expected, allocating the needed memory at initialization time is faster. It’s not a big difference but make it a bit faster.

Related Article

map is used when unmarshaling JSON data without defining struct.

Comments

Copied title and URL