Go Dictionary Fundamentals

GoGoBeginner
Practice Now

Introduction

Welcome Gophers to this new chapter.

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

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

Knowledge Points:

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

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("`Go`")) -.-> go/BasicsGroup(["`Basics`"]) go(("`Go`")) -.-> go/FunctionsandControlFlowGroup(["`Functions and Control Flow`"]) go(("`Go`")) -.-> go/DataTypesandStructuresGroup(["`Data Types and Structures`"]) go(("`Go`")) -.-> go/ObjectOrientedProgrammingGroup(["`Object-Oriented Programming`"]) go/BasicsGroup -.-> go/variables("`Variables`") go/FunctionsandControlFlowGroup -.-> go/for("`For`") go/DataTypesandStructuresGroup -.-> go/maps("`Maps`") go/FunctionsandControlFlowGroup -.-> go/range("`Range`") go/FunctionsandControlFlowGroup -.-> go/functions("`Functions`") go/DataTypesandStructuresGroup -.-> go/pointers("`Pointers`") go/DataTypesandStructuresGroup -.-> go/strings("`Strings`") go/ObjectOrientedProgrammingGroup -.-> go/struct_embedding("`Struct Embedding`") subgraph Lab Skills go/variables -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/for -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/maps -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/range -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/functions -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/pointers -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/strings -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} go/struct_embedding -.-> lab-149080{{"`Go Dictionary Fundamentals`"}} end

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 map?

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

Image Description

Declaration of Dictionaries

For a new data structure, the first thing we need to learn is 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

But 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
}

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, dictionaries, 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.

Then, 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(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 nil = "= =$"
    fmt.Println(nil)
}

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["shiyanlou"] = 1       // Add data to the dictionary
    fmt.Println(m)
}

The program output is as follows:

map[shiyanlou: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["shiyanlou"] = 666      // Add data to the dictionary
    fmt.Println(m)
}

The output of the program is as follows:

map[shiyanlou:666]

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

Manually Initialize an Empty Dictionary

In the previous section, we demonstrated using the make keyword to initialize a dictionary.

But we can also initialize a dictionary manually using the specific method.

Write the following code in map.go:

package main

import "fmt"

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

The program output is as follows:

map[LabEx:666]

This code is similar to directly declaring a dictionary, but the difference is that we add a pair of empty curly brackets {} after the declaration.

This indicates that m has been assigned an empty-initialized dictionary.

Thus, this dictionary can also be used.

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{
        "shiyanlou": 777,
        "Windows":   11, // The comma after the last element should not be omitted
    }
    m["LabEx"] = 666
    fmt.Println(m)
}

The program output is as follows:

map[Windows:11 LabEx:666 shiyanlou:777]

Note that when initializing a dictionary in Go, a comma needs to be added after the last element.

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["LabEx"] = 666

Note: In Go, each key in a map should be unique in the keys.

Write the following code in map.go:

package main

import "fmt"

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

The program output is as follows:

map[LabEx: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 do 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{
        "shiyanlou": 777,
        "Windows":   11,
        "LabEx":   666,
    }
    fmt.Println(m)
    delete(m, "Windows")
    fmt.Println(m)
}

The program output is as follows:

map[Windows:11 LabEx:666 shiyanlou:777]
map[LabEx:666 shiyanlou: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 experiment.

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{
        "shiyanlou": 0,
    }
    fmt.Print("The value of Windows is: ")
    fmt.Println(m["Windows"])
}

The program output is as follows:

The value of Windows 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{
        "shiyanlou": 0,
    }
    fmt.Print("The value of Windows is: ")
    fmt.Println(m["Windows"])
    fmt.Print("The value of shiyanlou is: ")
    fmt.Println(m["shiyanlou"])
}

The program output is as follows:

The value of Windows is: 0
The value of shiyanlou 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:

Windows, ok := m["Windows"]

Modify map.go:

package main

import "fmt"

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

The program output is as follows:

The value of Windows is: 0 Does it exist? false
The value of shiyanlou 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 some specific 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{
        "shiyanlou": 777,
        "Windows":   11,
        "LabEx":   666,
    }
    for key, value := range m {
        fmt.Println(key, value)
    }
}

The program output is as follows:

Windows 11
LabEx 666
shiyanlou 777

From the output, we can see that the way to iterate over a dictionary is very similar to iterating over slices or arrays, so we won't go into it in more detail.

Summary

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

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

In the next experiment, we will learn about advanced topics related to dictionaries, building on what we learned in this experiment.