-
Notifications
You must be signed in to change notification settings - Fork 47
0x03. Working with Collection
- Working with Arrays
- Working with Slices
- Working with Maps
- Credits
An array is a fixed-size collection of elements of the same type. The elements of the array are stored sequentially and can be accessed using their index
.
You can declare an array of length n and type T like so
var a[n]T
For example, here is how you can declare an array of 10 integers
// An array of 10 integers
var a[10]int
Now let’s see a complete example
package main
import "fmt"
func main() {
var x [5]int // An array of 5 integers
fmt.Println(x)
var y [8]string // An array of 8 strings
fmt.Println(y)
var z [3]complex128 // An array of 3 complex numbers
fmt.Println(z)
}
# Output
[0 0 0 0 0]
[ ]
[(0+0i) (0+0i) (0+0i)]
By default, all the array elements are initialized with the zero value of the corresponding array type.
For example, if we declare an integer array, all the elements will be initialized with 0. If we declare a string array, all the elements will be initialized with an empty string "", and so on.
The elements of an array are stored sequentially and can be accessed by their index
. The index starts from zero and ends at length - 1
.
package main
import "fmt"
func main() {
var x [5]int // An array of 5 integers
x[0] = 100
x[1] = 101
x[3] = 103
x[4] = 105
fmt.Printf("x[0] = %d, x[1] = %d, x[2] = %d\n", x[0], x[1], x[2])
fmt.Println("x = ", x)
}
# Output
x[0] = 100, x[1] = 101, x[2] = 0
x = [100 101 0 103 105]
In the above example, Since we didn’t assign any value to x[2]
, it has the value 0
(The zero value for integers).
You can declare and initialize an array at the same time like this
// Declaring and initializing an array at the same time
var a = [5]int{2, 4, 6, 8, 10}
The expression on the right-hand side of the above statement is called an array literal.
Note that we do not need to specify the type of the variable a
as in var a [5]int
, because the compiler can automatically infer the type from the expression on the right hand side.
You can also use Golang’s short variable declaration for declaring and initializing an array. The above array declaration can also be written as below inside any function
// Short hand declaration
a := [5]int{2, 4, 6, 8, 10}
Here is a complete example
package main
import "fmt"
func main() {
// Declaring and initializing an array at the same time
var a = [5]int{2, 4, 6, 8, 10}
fmt.Println(a)
// Short declaration for declaring and initializing an array
b := [5]int{2, 4, 6, 8, 10}
fmt.Println(b)
// You don't need to initialize all the elements of the array.
// The un-initialized elements will be assigned the zero value of the corresponding array type
c := [5]int{2}
fmt.Println(c)
}
# Output
[2 4 6 8 10]
[2 4 6 8 10]
[2 0 0 0 0]
You can also omit the size declaration from the initialization expression of the array, and let the compiler count the number of elements for you
package main
import "fmt"
func main() {
// Letting Go compiler infer the length of the array
a := [...]int{3, 5, 7, 9, 11, 13, 17}
fmt.Println(a)
}
# Output
[3 5 7 9 11 13 17]
The length of an array is part of its type. So the array a[5]int
and a[10]int
are completely distinct types, and you cannot assign one to the other.
This also means that you cannot resize an array, because resizing an array would mean changing its type, and you cannot change the type of a variable in Golang.
This also means that you cannot resize an array, because resizing an array would mean changing its type, and you cannot change the type of a variable in Golang.
package main
func main() {
var a = [5]int{3, 5, 7, 9, 11}
var b [10]int = a // Error, a and b are distinct types
}
Arrays in Golang are value types unlike other languages like C, C++, and Java where arrays are reference types.
This means that when you assign an array to a new variable or pass an array to a function, the entire array is copied. So if you make any changes to this copied array, the original array won’t be affected and will remain unchanged.
Here is an example
package main
import "fmt"
func main() {
a1 := [5]string{"English", "Japanese", "Spanish", "French", "Hindi"}
a2 := a1 // A copy of the array `a1` is assigned to `a2`
a2[1] = "German"
fmt.Println("a1 = ", a1) // The array `a1` remains unchanged
fmt.Println("a2 = ", a2)
}
# Output
a1 = [English Japanese Spanish French Hindi]
a2 = [English German Spanish French Hindi]
You can use the for loop to iterate over an array like so
package main
import "fmt"
func main() {
names := [3]string{"Mark Zuckerberg", "Bill Gates", "Larry Page"}
for i := 0; i < len(names); i++ {
fmt.Println(names[i])
}
}
# Output
Mark Zuckerberg
Bill Gates
Larry Page
The len()
function in the above example is used to find the length of the array.
Let’s see another example. In the example below, we find the sum of all the elements of the array by iterating over the array, and adding the elements one by one to the variable sum
package main
import "fmt"
func main() {
a := [4]float64{3.5, 7.2, 4.8, 9.5}
sum := float64(0)
for i := 0; i < len(a); i++ {
sum = sum + a[i]
}
fmt.Printf("Sum of all the elements in array %v = %f\n", a, sum)
}
# Output
Sum of all the elements in array [3.5 7.2 4.8 9.5] = 25.000000
Golang provides a more powerful form of for
loop using the range
operator. Here is how you can use the range
operator with for
loop to iterate over an array
package main
import "fmt"
func main() {
daysOfWeek := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
for index, value := range daysOfWeek {
fmt.Printf("Day %d of week = %s\n", index, value)
}
}
# Output
Day 0 of week = Mon
Day 1 of week = Tue
Day 2 of week = Wed
Day 3 of week = Thu
Day 4 of week = Fri
Day 5 of week = Sat
Day 6 of week = Sun
Let’s now write the same sum
example that we wrote with normal for
loop using range
form of the for
loop
package main
import "fmt"
func main() {
a := [4]float64{3.5, 7.2, 4.8, 9.5}
sum := float64(0)
for index, value := range a {
sum = sum + value
}
fmt.Printf("Sum of all the elements in array %v = %f", a, sum)
}
When you run the above program, it’ll generate an error like this
# Output
./array_iteration_range.go:9:13: index declared and not used
Go compiler doesn’t allow creating variables that are never used. You can fix this by using an _
(underscore) in place of index
package main
import "fmt"
func main() {
a := [4]float64{3.5, 7.2, 4.8, 9.5}
sum := float64(0)
for _, value := range a {
sum = sum + value
}
fmt.Printf("Sum of all the elements in array %v = %f", a, sum)
}
The underscore (_
) is used to tell the compiler that we don’t need this variable. The above program now runs successfully and outputs the sum of the array
# Output
Sum of all the elements in array [3.5 7.2 4.8 9.5] = 25.000000
All the arrays that we created so far in this post are one dimensional. You can also create multi-dimensional arrays in Golang.
The following example demonstrates how to create multidimensional arrays
package main
import "fmt"
func main() {
a := [2][2]int{
{3, 5},
{7, 9}, // This trailing comma is mandatory
}
fmt.Println(a)
// Just like 1D arrays, you don't need to initialize all the elements in a multi-dimensional array.
// Un-initialized array elements will be assigned the zero value of the array type.
b := [3][4]float64{
{1, 3},
{4.5, -3, 7.4, 2},
{6, 2, 11},
}
fmt.Println(b)
}
# Output
[[3 5] [7 9]]
[[1 3 0 0] [4.5 -3 7.4 2] [6 2 11 0]]
A Slice is a segment of an array. Slices build on arrays and provide more power, flexibility, and convenience compared to arrays.
Just like arrays, Slices are indexable and have a length. But unlike arrays, they can be resized.
Internally, A Slice is just a reference to an underlying array. In this article, we’ll learn how to create and use slices, and also understand how they work under the hood.
A slice of type T
is declared using []T
. For example, Here is how you can declare a slice of type int
// Slice of type `int`
var s []int
The slice is declared just like an array except that we do not specify any size in the brackets []
.
You can create a slice using a slice literal like this
// Creating a slice using a slice literal
var s = []int{3, 5, 7, 9, 11, 13, 17}
The expression on the right-hand side of the above statement is a slice literal. The slice literal is declared just like an array literal, except that you do not specify any size in the square brackets []
.
When you create a slice using a slice literal, it first creates an array and then returns a slice reference to it.
Let’s see a complete example
package main
import "fmt"
func main() {
// Creating a slice using a slice literal
var s = []int{3, 5, 7, 9, 11, 13, 17}
// Short hand declaration
t := []int{2, 4, 8, 16, 32, 64}
fmt.Println("s = ", s)
fmt.Println("t = ", t)
}
# Output
s = [3 5 7 9 11 13 17]
t = [2 4 8 16 32 64]
Since a slice is a segment of an array, we can create a slice from an array.
To create a slice from an array a, we specify two indices low (lower bound) and high (upper bound) separated by a colon
// Obtaining a slice from an array `a`
a[low:high]
The above expression selects a
slice from the array a
. The resulting slice includes all the elements starting from index low
to high
, but excluding the element at index high
.
Let’s see an example to make things more clear
package main
import "fmt"
func main() {
var a = [5]string{"Alpha", "Beta", "Gamma", "Delta", "Epsilon"}
// Creating a slice from the array
var s []string = a[1:4]
fmt.Println("Array a = ", a)
fmt.Println("Slice s = ", s)
}
# Output
Array a = [Alpha Beta Gamma Delta Epsilon]
Slice s = [Beta Gamma Delta]
The low
and high
indices in the slice expression are optional. The default value for low
is 0
, and high
is the length of the slice.
package main
import "fmt"
func main() {
a := [5]string{"C", "C++", "Java", "Python", "Go"}
slice1 := a[1:4]
slice2 := a[:3]
slice3 := a[2:]
slice4 := a[:]
fmt.Println("Array a = ", a)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
fmt.Println("slice3 = ", slice3)
fmt.Println("slice4 = ", slice4)
}
# Output
Array a = [C C++ Java Python Go]
slice1 = [C++ Java Python]
slice2 = [C C++ Java]
slice3 = [Java Python Go]
slice4 = [C C++ Java Python Go]
A slice can also be created by slicing an existing slice.
package main
import "fmt"
func main() {
cities := []string{"New York", "London", "Chicago", "Beijing", "Delhi", "Mumbai", "Bangalore", "Hyderabad", "Hong Kong"}
asianCities := cities[3:]
indianCities := asianCities[1:5]
fmt.Println("cities = ", cities)
fmt.Println("asianCities = ", asianCities)
fmt.Println("indianCities = ", indianCities)
}
# Output
cities = [New York London Chicago Beijing Delhi Mumbai Bangalore Hyderabad Hong Kong]
asianCities = [Beijing Delhi Mumbai Bangalore Hyderabad Hong Kong]
indianCities = [Delhi Mumbai Bangalore Hyderabad]
Slices are reference types. They refer to an underlying array. Modifying the elements of a slice will modify the corresponding elements in the referenced array. Other slices that refer the same array will also see those modifications.
package main
import "fmt"
func main() {
a := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
slice1 := a[1:]
slice2 := a[3:]
fmt.Println("------- Before Modifications -------")
fmt.Println("a = ", a)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
slice1[0] = "TUE"
slice1[1] = "WED"
slice1[2] = "THU"
slice2[1] = "FRIDAY"
fmt.Println("\n-------- After Modifications --------")
fmt.Println("a = ", a)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
}
# Output
------- Before Modifications -------
a = [Mon Tue Wed Thu Fri Sat Sun]
slice1 = [Tue Wed Thu Fri Sat Sun]
slice2 = [Thu Fri Sat Sun]
-------- After Modifications --------
a = [Mon TUE WED THU FRIDAY Sat Sun]
slice1 = [TUE WED THU FRIDAY Sat Sun]
slice2 = [THU FRIDAY Sat Sun]
A slice consists of three things:
- A pointer (reference) to an underlying array.
- The length of the segment of the array that the slice contains.
- The capacity (the maximum size up to which the segment can grow).
Let’s consider the following array and the slice obtained from it as an example:
var a = [6]int{10, 20, 30, 40, 50, 60}
var s = [1:4]
Here is how the slice s in the above example is represented:
The length of the slice is the number of elements in the slice, which is 3
in the above example.
The capacity is the number of elements in the underlying array starting from the first element in the slice. It is 5
in the above example.
You can find the length and capacity of a slice using the built-in functions len()
and cap()
package main
import "fmt"
func main() {
a := [6]int{10, 20, 30, 40, 50, 60}
s := a[1:4]
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [20 30 40], len = 3, cap = 5
A slice’s length can be extended up to its capacity by re-slicing it. Any attempt to extend its length beyond the available capacity will result in a runtime error.
Check out the following example to understand how re-slicing a given slice changes its length and capacity:
package main
import "fmt"
func main() {
s := []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}
fmt.Println("Original Slice")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
s = s[1:5]
fmt.Println("\nAfter slicing from index 1 to 5")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
s = s[:8]
fmt.Println("\nAfter extending the length")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
s = s[2:]
fmt.Println("\nAfter dropping the first two elements")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
Original Slice
s = [10 20 30 40 50 60 70 80 90 100], len = 10, cap = 10
After slicing from index 1 to 5
s = [20 30 40 50], len = 4, cap = 9
After extending the length
s = [20 30 40 50 60 70 80 90], len = 8, cap = 9
After dropping the first two elements
s = [40 50 60 70 80 90], len = 6, cap = 7
Now that we know about the length and capacity of a slice. Let’s look at another way to create a slice.
Golang provides a library function called make()
for creating slices. Following is the signature of make()
function:
func make([]T, len, cap) []T
The make function takes a type, a length, and an optional capacity. It allocates an underlying array with size equal to the given capacity, and returns a slice that refers to that array.
package main
import "fmt"
func main() {
// Creates an array of size 10, slices it till index 5, and returns the slice reference
s := make([]int, 5, 10)
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [0 0 0 0 0], len = 5, cap = 10
The capacity parameter in the make()
function is optional. When omitted, it defaults to the specified length:
package main
import "fmt"
func main() {
// Creates an array of size 5, and returns a slice reference to it
s := make([]int, 5)
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [0 0 0 0 0], len = 5, cap = 5
The zero value of a slice is nil. A nil slice doesn’t have any underlying array, and has a length and capacity of 0:
package main
import "fmt"
func main() {
var s []int
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
if s == nil {
fmt.Println("s is nil")
}
}
# Output
s = [], len = 0, cap = 0
s is nil
The copy()
function copies elements from one slice to another. Its signature looks like this -
func copy(dst, src []T) int
It takes two slices - a destination slice, and a source slice. It then copies elements from the source to the destination and returns the number of elements that are copied.
Note that the elements are copied only if the destination slice has sufficient capacity.
package main
import "fmt"
func main() {
src := []string{"Sublime", "VSCode", "IntelliJ", "Eclipse"}
dest := make([]string, 2)
numElementsCopied := copy(dest, src)
fmt.Println("src = ", src)
fmt.Println("dest = ", dest)
fmt.Println("Number of elements copied from src to dest = ", numElementsCopied)
}
# Output
src = [Sublime VSCode IntelliJ Eclipse]
dest = [Sublime VSCode]
Number of elements copied from src to dest = 2
The append()
function appends new elements at the end of a given slice. Following is the signature of append function.
func append(s []T, x ...T) []T
It takes a slice and a variable number of arguments x …T. It then returns a new slice containing all the elements from the given slice as well as the new elements.
If the given slice doesn’t have sufficient capacity to accommodate new elements then a new underlying array is allocated with bigger capacity. All the elements from the underlying array of the existing slice are copied to this new array, and then the new elements are appended.
However, if the slice has enough capacity to accommodate new elements, then the append()
function re-uses its underlying array and appends new elements to the same array.
Let’s see an example to understand things better:
package main
import "fmt"
func main() {
slice1 := []string{"C", "C++", "Java"}
slice2 := append(slice1, "Python", "Ruby", "Go")
fmt.Printf("slice1 = %v, len = %d, cap = %d\n", slice1, len(slice1), cap(slice1))
fmt.Printf("slice2 = %v, len = %d, cap = %d\n", slice2, len(slice2), cap(slice2))
slice1[0] = "C#"
fmt.Println("\nslice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
}
# Output
slice1 = [C C++ Java], len = 3, cap = 3
slice2 = [C C++ Java Python Ruby Go], len = 6, cap = 6
slice1 = [C# C++ Java]
slice2 = [C C++ Java Python Ruby Go]
In the above example, since slice1
has capacity 3, it can’t accommodate more elements. So a new underlying array is allocated with bigger capacity when we append more elements to it.
So if you modify slice1
, slice2
won’t see those changes because it refers to a different array.
But what if slice1
had enough capacity to accommodate new elements? Well, in that case, no new array would be allocated, and the elements would be added to the same underlying array.
Also, in that case, changes to slice1
would affect slice2
as well because both would refer to the same underlying array. This is demonstrated in the following example:
package main
import "fmt"
func main() {
slice1 := make([]string, 3, 10)
copy(slice1, []string{"C", "C++", "Java"})
slice2 := append(slice1, "Python", "Ruby", "Go")
fmt.Printf("slice1 = %v, len = %d, cap = %d\n", slice1, len(slice1), cap(slice1))
fmt.Printf("slice2 = %v, len = %d, cap = %d\n", slice2, len(slice2), cap(slice2))
slice1[0] = "C#"
fmt.Println("\nslice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
}
# Output
slice1 = [C C++ Java], len = 3, cap = 10
slice2 = [C C++ Java Python Ruby Go], len = 6, cap = 10
slice1 = [C# C++ Java]
slice2 = [C# C++ Java Python Ruby Go]
When you append values to a nil slice, it allocates a new slice and returns the reference of the new slice.
package main
import "fmt"
func main() {
var s []string
// Appending to a nil slice
s = append(s, "Cat", "Dog", "Lion", "Tiger")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [Cat Dog Lion Tiger], len = 4, cap = 4
You can directly append one slice to another using the ...
operator. This operator expands the slice to a list of arguments. The following example demonstrates its usage:
package main
import "fmt"
func main() {
slice1 := []string{"Jack", "John", "Peter"}
slice2 := []string{"Bill", "Mark", "Steve"}
slice3 := append(slice1, slice2...)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
fmt.Println("After appending slice1 & slice2 = ", slice3)
}
# Output
slice1 = [Jack John Peter]
slice2 = [Bill Mark Steve]
After appending slice1 & slice2 = [Jack John Peter Bill Mark Steve]
Slices can be of any type. They can also contain other slices. The example below creates a slice of slices -
package main
import "fmt"
func main() {
s := [][]string{
{"India", "China"},
{"USA", "Canada"},
{"Switzerland", "Germany"},
}
fmt.Println("Slice s = ", s)
fmt.Println("length = ", len(s))
fmt.Println("capacity = ", cap(s))
}
# Output
Slice s = [[India China] [USA Canada] [Switzerland Germany]]
length = 3
capacity = 3
You can iterate over a slice in the same way you iterate over an array. Following are two ways of iterating over a slice:
package main
import "fmt"
func main() {
countries := []string{"India", "America", "Russia", "England"}
for i := 0; i < len(countries); i++ {
fmt.Println(countries[i])
}
}
# Output
India
America
Russia
England
package main
import "fmt"
func main() {
primeNumbers := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
for index, number := range primeNumbers {
fmt.Printf("PrimeNumber(%d) = %d\n", index+1, number)
}
}
# Output
PrimeNumber(1) = 2
PrimeNumber(2) = 3
PrimeNumber(3) = 5
PrimeNumber(4) = 7
PrimeNumber(5) = 11
PrimeNumber(6) = 13
PrimeNumber(7) = 17
PrimeNumber(8) = 19
PrimeNumber(9) = 23
PrimeNumber(10) = 29
Ignoring the index
from the range
form of for
loop using Blank identifier
The range
form of for
loop gives you the index
and the value
at that index in each iteration. If you don’t want to use the index
, then you can discard it by using an underscore _
.
The underscore (_
) is called the blank identifier. It is used to tell Go compiler that we don’t need this value.
package main
import "fmt"
func main() {
numbers := []float64{3.5, 7.4, 9.2, 5.4}
sum := 0.0
for _, number := range numbers {
sum += number
}
fmt.Printf("Total Sum = %.2f\n", sum)
}
# Output
Total Sum = 25.50
A map is an unordered collection of key-value pairs. It maps keys to values. The keys are unique within a map while the values may not be.
The map data structure is used for fast lookups, retrieval, and deletion of data based on keys. It is one of the most used data structures in computer science.
Go provides a built-in map type. In this article, we’ll learn how to use Golang’s built-in map type.
A map is declared using the following syntax:
var m map[KeyType]ValueType
For example, Here is how you can declare a map of string
keys to int
values:
var m map[string]int
The zero value of a map is nil
. A nil
map has no keys. Moreover, any attempt to add keys to a nil
map will result in a runtime error.
Let’s see an example:
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m)
if m == nil {
fmt.Println("m is nil")
}
// Attempting to add keys to a nil map will result in a runtime error
// m["one hundred"] = 100
}
# Output
map[]
m is nil
If you uncomment the statement m["one hundred"] = 100
, the program will generate the following error:
panic: assignment to entry in nil map
It is, therefore, necessary to initialize a map before adding items to it.
You can initialize a map using the built-in make()
function. You just need to pass the type of the map to the make()
function as in the example below. The function will return an initialized and ready to use map
// Initializing a map using the built-in `make()` function
var m = make(map[string]int)
Let’s see a complete example:
package main
import "fmt"
func main() {
var m = make(map[string]int)
fmt.Println(m)
if m == nil {
fmt.Println("m is nil")
} else {
fmt.Println("m is not nil")
}
// make() function returns an initialized and ready to use map.
// Since it is initialized, you can add new keys to it.
m["one hundred"] = 100
fmt.Println(m)
}
# Output
map[]
m is not nil
map[one hundred:100]
A map literal is a very convenient way to initialize a map with some data. You just need to pass the key-value pairs separated by colon inside curly braces like this
var m = map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
Note that the last trailing comma is necessary, otherwise, you’ll get a compiler error.
Let’s check out a complete example:
package main
import "fmt"
func main() {
var m = map[string]int{
"one": 1,
"two": 2,
"three": 3,
"four": 4,
"five": 5, // Comma is necessary
}
fmt.Println(m)
}
# Output
map[one:1 two:2 three:3 four:4 five:5]
You can also create an empty map using a map literal by leaving the curly braces empty
// Initialize an empty map
var m = map[string]int{}
The above statement is functionally identical to using the make()
function.
You can add new items to an initialized map using the following syntax
m[key] = value
The following example initializes a map using the make() function and adds some new items to it
package main
import "fmt"
func main() {
// Initializing a map
var tinderMatch = make(map[string]string)
// Adding keys to a map
tinderMatch["Rajeev"] = "Angelina" // Assigns the value "Angelina" to the key "Rajeev"
tinderMatch["James"] = "Sophia"
tinderMatch["David"] = "Emma"
fmt.Println(tinderMatch)
/*
Adding a key that already exists will simply override
the existing key with the new value
*/
tinderMatch["Rajeev"] = "Jennifer"
fmt.Println(tinderMatch)
}
# Output
map[Rajeev:Angelina James:Sophia David:Emma]
map[Rajeev:Jennifer James:Sophia David:Emma]
If you try to add a key that already exists in the map, then it will simply be overridden by the new value.
You can retrieve the value assigned to a key in a map using the syntax m[key]
. If the key exists in the map, you’ll get the assigned value. Otherwise, you’ll get the zero value of the map’s value type.
Let’s check out an example to understand this:
package main
import "fmt"
func main() {
var personMobileNo = map[string]string{
"John": "+33-8273658526",
"Steve": "+1-8579822345",
"David": "+44-9462834443",
}
var mobileNo = personMobileNo["Steve"]
fmt.Println("Steve's Mobile No : ", mobileNo)
// If a key doesn't exist in the map, we get the zero value of the value type
mobileNo = personMobileNo["Jack"]
fmt.Println("Jack's Mobile No : ", mobileNo)
}
# Output
Steve's Mobile No : +1-8579822345
Jack's Mobile No :
In the above example, since the key "Jack"
doesn’t exist in the map, we get the zero value of the map’s value type. Since the map’s value type is string
, we get " "
.
Unlike other languages, we do not get a runtime error in Golang if the key doesn’t exist in the map.
But what if you want to check for the existence of a key? In the above example, the map would return " "
even if the key "Jack"
existed with the value " "
. So how do we distinguish between cases where a key exists with the value equal to the zero value of the value type, and the absence of a key?
Well, let’s find out.
When you retrieve the value assigned to a given key using the syntax map[key]
, it returns an additional boolean value as well which is true
if the key exists in the map, and false
if it doesn’t exist.
So you can check for the existence of a key in a map by using the following two-value assignment:
value, ok := m[key]
The boolean variable ok
will be true
if the key exists, and false
otherwise.
Consider the following map for example. It maps employeeIds to names:
var employees = map[int]string{
1001: "Rajeev",
1002: "Sachin",
1003: "James",
}
Accessing the key 1001
will return "Rajeev"
and true
, since the key 1001
exists in the map:
name, ok := employees[1001] // "Rajeev", true
However, If you try to access a key that doesn’t exist, then the map will return an empty string ""
(zero value of strings), and false
.
name, ok := employees[1010] // "", false
If you just want to check for the existence of a key without retrieving the value associated with that key, then you can use an _
(underscore) in place of the first value
_, ok := employees[1005]
Now let’s check out a complete example:
package main
import "fmt"
func main() {
var employees = map[int]string{
1001: "John",
1002: "Steve",
1003: "Maria",
}
printEmployee(employees, 1001)
printEmployee(employees, 1010)
if isEmployeeExists(employees, 1002) {
fmt.Println("EmployeeId 1002 found")
}
}
func printEmployee(employees map[int]string, employeeId int) {
if name, ok := employees[employeeId]; ok {
fmt.Printf("name = %s, ok = %v\n", name, ok)
} else {
fmt.Printf("EmployeeId %d not found\n", employeeId)
}
}
func isEmployeeExists(employees map[int]string, employeeId int) bool {
_, ok := employees[employeeId]
return ok
}
# Output
name = Rajeev, ok = true
EmployeeId 1010 not found
EmployeeId 1002 found
In the above example, I’ve used a short declaration in the if statement to initialize the name
and ok
values, and then test the boolean value ok
. It makes the code more concise.
You can delete a key from a map using the built-in delete()
function. The syntax looks like this
// Delete the `key` from the `map`
delete(map, key)
The delete()
function doesn’t return any value. Also, it doesn’t do anything if the key doesn’t exist in the map.
Here is a complete example:
package main
import "fmt"
func main() {
var fileExtensions = map[string]string{
"Python": ".py",
"C++": ".cpp",
"Java": ".java",
"Golang": ".go",
"Kotlin": ".kt",
}
fmt.Println(fileExtensions)
delete(fileExtensions, "Kotlin")
// delete function doesn't do anything if the key doesn't exist
delete(fileExtensions, "Javascript")
fmt.Println(fileExtensions)
}
# Output
map[Python:.py C++:.cpp Java:.java Golang:.go Kotlin:.kt]
map[Python:.py C++:.cpp Java:.java Golang:.go]
Maps are reference types. When you assign a map to a new variable, they both refer to the same underlying data structure. Therefore changes done by one variable will be visible to the other
package main
import "fmt"
func main() {
var m1 = map[string]int{
"one": 1,
"two": 2,
"three": 3,
"four": 4,
"five": 5,
}
var m2 = m1
fmt.Println("m1 = ", m1)
fmt.Println("m2 = ", m2)
m2["ten"] = 10
fmt.Println("\nm1 = ", m1)
fmt.Println("m2 = ", m2)
}
# Output
m1 = map[one:1 two:2 three:3 four:4 five:5]
m2 = map[one:1 two:2 three:3 four:4 five:5]
m1 = map[one:1 two:2 three:3 four:4 five:5 ten:10]
m2 = map[one:1 two:2 three:3 four:4 five:5 ten:10]
The same concept applies when you pass a map to a function. Any changes done to the map inside the function is also visible to the caller.
You can iterate over a map using range
form of the for loop. It gives you the key, value pair in every iteration
package main
import "fmt"
func main() {
var personAge = map[string]int{
"Rajeev": 25,
"James": 32,
"Sarah": 29,
}
for name, age := range personAge {
fmt.Println(name, age)
}
}
# Output
James 32
Sarah 29
Rajeev 25
Note that, A map is an unordered collection and therefore the iteration order of a map is not guaranteed to be the same every time you iterate over it.
So if you run the above program multiple times, you’ll get the results in different orders.
ⓒ 2019 Phú, Trần Phong & NordicCoder