Go Dictionary Fundamentals

GolangGolangBeginner
Practice Now

Introduction

Welcome Gophers to this new chapter.

In this chapter, we will learn about the basic usage of maps and analyze the presence of nil in Go.

After completing this chapter, you will be able to apply maps to your daily development tasks.

Knowledge Points:

  • Declaring a map
  • Initializing a map
  • Adding, deleting, updating, and searching for map elements
  • Checking if a map element exists
  • Iterating over a map

Introduction to Dictionaries

So, what is a dictionary?

In computer science, a dictionary is a special data structure that consists of key-value pairs.

Key-value pair: A pair of elements, where one element is called the key, and the other is called the value.

So why do we need to use a dictionary?

Because dictionaries have high efficiency when it comes to operations like adding, deleting, updating, and searching for elements, I believe you will all find this data structure very useful.

Image Description

Declaration of Maps

For a new data structure, the first step is to learn how to declare it.

For a map, the way to define it is:

var variableName map[keyType]valueType

For example:

var a map[string]int

However, because a map is a reference type, if we declare it like the code above, we will encounter a problem.

Let's create a map.go file in the ~/project directory:

touch ~/project/map.go

Write the following code in map.go:

package main

func main() {
    // Declare a map named m with key type as string and value type as int
    var m map[string]int
    // Add a data entry "labex"->1 to it
    m["labex"] = 1 // Program crashes
}

Run the program:

go run map.go

We found that the program crashed and threw an error:

panic: assignment to entry in nil map

This means that assigning an element to a nil map is an erroneous behavior.

This is because, for data structures like slices, maps, channels, and pointers, they must be initialized before use.

And nil is the initial default value for a map, which means that no memory is allocated for the variable during the definition.

The Initial Value nil

In the previous section, we mentioned that it is an erroneous behavior to assign an element to a nil map.

Let's take this opportunity to explore the true meaning of nil in Go.

The essence of nil is a pre-declared identifier.

For basic data types, their initial values are different:

  • Boolean values
  • Numeric values
  • Strings

However, for slices, dictionaries, pointers, channels, and functions, their initial value is nil. It is not the initial default value that we are familiar with.

This is reflected in the instantiation objects that are assigned nil. Although they can be printed, they cannot be used.

In addition, there are some points to note about nil.

Incomparability of Different Types of nil

package main

import "fmt"

func main() {
    var m map[int]string
    var p *int
    fmt.Printf("%v", m == p)
}

The program output is as follows:

invalid operation: m == p (mismatched types map[int]string and *int)

That is, we cannot compare the nil of an int type pointer with the nil of a map.

They are not comparable.

nil is not a keyword

package main

import "fmt"

func main() {
    var nilValue = "= =$"
    fmt.Println(nilValue)
}

The program output is as follows:

= =$

We can define a variable named nil and it can be compiled without errors. However, we strongly recommend that you do not do this in actual development.

Incomparability of nil

package main

import "fmt"

func main() {
    fmt.Println(nil == nil)
}

The program output is as follows:

invalid operation: nil == nil (operator == not defined on nil)

Declaration with the make Keyword

After understanding the meaning of nil, let's solve the problem of memory allocation for dictionaries.

Here we use the make keyword to allocate memory.

The make function generates a value of a specified type that has been initialized as its return value.

package main

import "fmt"

func main() {
    var m map[string]int     // Declare a dictionary
    m = make(map[string]int) // Allocate memory to the dictionary
    m["labex"] = 1       // Add data to the dictionary
    fmt.Println(m)
}

The program output is as follows:

map[labex:1]

The above code demonstrates how to allocate memory to a dictionary that has been declared using the make keyword.

We can also simplify this process:

Write the following code in map.go:

package main

import "fmt"

func main() {
    m := make(map[string]int) // Declare and initialize a dictionary
    m["labex"] = 666      // Add data to the dictionary
    fmt.Println(m)
}

The output of the program is as follows:

map[labex:666]

The above code shows how to declare a dictionary using the make keyword.

Manually Initializing an Empty Map

In the previous section, we demonstrated how to use the make keyword to initialize a map. Now, let’s explore another method: using the literal syntax to manually initialize an empty map.

This method is concise and allows you to create an empty map that is ready to use without allocating memory explicitly.

Write the following code in map.go:

package main

import "fmt"

func main() {
    // Using literal syntax to initialize an empty map
    m := map[string]int{}

    // Adding elements to the map
    m["labex"] = 777
    m["labs"] = 11

    fmt.Println(m) // Output the map with added elements
}

The syntax map[keyType]valueType{} creates an empty map ready for use. Once initialized, you can add elements to the map using the syntax map[key] = value.

When you run the above code, the program will output:

map[labex:777 labs:11]

Benefits of Manual Initialization:

  • Provides a quick way to create an empty map without using the make keyword.
  • Useful when you need to start with an empty map and populate it dynamically.

Actually Initializing the Dictionary

Since we can initialize an empty dictionary, we can also give it some initial values.

Write the following code in map.go:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
    }
    m["labby"] = 666
    fmt.Println(m)
}

The program output is as follows:

map[labby:666 labs:11 labex:777]

Note that when initializing a dictionary in Go, a comma must be added after each element, including the last one.

Adding and Updating Dictionary Elements

Adding elements to a dictionary is very simple, as demonstrated in the code example above.

The syntax is:

dictionaryInstance[keyToAdd] = valueToAdd

For example:

m := map[string]int{}
m["labby"] = 666

Note: In Go, each key in a map must be unique.

Write the following code in map.go:

package main

import "fmt"

func main() {
    m := map[string]int{}
    m["labby"] = 666
    m["labby"] = 777
    fmt.Println(m)
}

The program output is as follows:

map[labby:777]

We found that the value of the output was changed to 777. That is, if we write different values to the same key, the corresponding value will be updated to the new value.

This is how we update or modify a dictionary.

Deleting Dictionary Elements

How can we delete an element from a dictionary?

We need to use the built-in delete function.

Write the following code in map.go:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
        "labby": 666,
    }
    fmt.Println(m)
    delete(m, "labs")
    fmt.Println(m)
}

The program output is as follows:

map[labby:666 labs:11 labex:777]
map[labby:666 labex:777]

The delete function takes two arguments. The first argument is the dictionary to be operated on, and the second argument is the key to be deleted.

Of course, the delete function has other uses, but we will only discuss its usage in dictionaries in this lab.

Searching for Dictionary Elements

What happens when we search for an element in a dictionary that does not exist?

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
}

The program output is as follows:

The value of labs is: 0

We found that if the element does not exist in the dictionary, querying it will return the default value of the value type.

What about the key in the dictionary whose value is 0?

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
    fmt.Print("The value of labex is: ")
    fmt.Println(m["labex"])
}

The program output is as follows:

The value of labs is: 0
The value of labex is: 0

We found that for a dictionary, the presentation of a value that does not exist and a value that exists but has a default value is the same.

This causes us a lot of confusion. How do we solve it?

As it turns out, the developers of Go had already thought of this issue. When we query an element in a dictionary, there will be one or two variable return values.

That is:

labs, ok := m["labs"]

Modify map.go:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    labs, ok := m["labs"]
    fmt.Print("The value of labs is: ")
    fmt.Print(labs)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok)
    labex, ok2 := m["labex"]
    fmt.Print("The value of labex is: ")
    fmt.Print(labex)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok2)
}

The program output is as follows:

The value of labs is: 0 Does it exist? false
The value of labex is: 0 Does it exist? true

Now, we can use the second return value of the query to determine whether the returned result is an existing initial default value or a nonexistent initial default value.

Iterating Over Dictionaries

In certain scenarios, we need to iterate over an entire dictionary, query each key-value pair, and process them. How can we achieve this?

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "labex": 777,
        "labs":   11,
        "labby":     666,
    }
    for key, value := range m {
        fmt.Println(key, value)
    }
}

The program output is as follows:

labby 666
labs 11
labex 777

From the output, we can see that the way to iterate over a dictionary is very similar to iterating over slices or arrays.

Summary

In this lab, we learned about the fundamental usage of dictionaries, including:

  • The declaration of dictionaries and how to address the declaration issue
  • The characteristics of the initial value nil
  • The methods of initializing dictionaries
  • Adding, removing, updating, and searching for elements in dictionaries
  • Detecting the existence of dictionary elements
  • Iterating over dictionaries