DEV Community

nadirbasalamah
nadirbasalamah

Posted on • Updated on

Golang Tutorial - 5 Array, Slice and Map + slices and maps package

HOT UPDATE: This post includes a tutorial on slices and maps package usage.

In programming, storing multiple values or data can be done using data structure. The data structure is a "container" that can be used to store multiple data or values. The examples of data structure in Go are array, slice, and map.

Array

The array is a data structure that can store multiple values of the same type and use a certain size. The array isn't recommended to be used but it's good to know the characteristics of the array in Go. Because using a certain size, the size or length can't be changed automatically.

This is the illustration of an array data structure.

Array data structure

The anatomy of array declaration is like this:

//[size of array]data type{values}
data := [5]int{1, 2, 3, 4, 5}
Enter fullscreen mode Exit fullscreen mode

Adding data into an array can be done using a certain index like this:

//declaring empty array
data := [2]int{}
data[0] = 1 //assign data "1" into index 0
data[1] = 2 //assign data "2" into index 1
Enter fullscreen mode Exit fullscreen mode

Notice that accessing or assigning data from the beginning of the array started from index 0.
If the number of data inserted is larger than the array's size or length. It throws an error.
For example:

//declaring empty array
data := [2]int{}
data[0] = 1
data[1] = 2
data[2] = 3 //throw an error : invalid array index 2 (out of bounds for 2-element array)
Enter fullscreen mode Exit fullscreen mode

Accessing each data from the array can be done using a for loop.

Using regular for loop is like this:

data := [5]string{"NodeJS", "PHP", "Python", "Go", "Java"} //declare an array of string
for i := 0; i < len(data); i++ {  //len(data) is used to get the length of array
    fmt.Println(data[i]) //access data from index " i "
}
Enter fullscreen mode Exit fullscreen mode

The output :

NodeJS
PHP
Python
Go
Java
Enter fullscreen mode Exit fullscreen mode

From that code, there is the len(data) syntax, which is a method that can be used to get the size or length of an array or slice.
Using for range loop is like this:

data := [5]string{"NodeJS", "PHP", "Python", "Go", "Java"} //declare an array of string
//retrieve "v" (value) from array, ignore index using " _ " notation
for _, v := range data {
    fmt.Println(v)
}
Enter fullscreen mode Exit fullscreen mode

The output same as the above.
Using fmt.Println can be used also to check the values stored inside the array.

data := [5]string{"NodeJS", "PHP", "Python", "Go", "Java"} //declare an array of string
fmt.Println(data)
Enter fullscreen mode Exit fullscreen mode

Output :

[NodeJS PHP Python Go Java]
Enter fullscreen mode Exit fullscreen mode

Slice

A slice is a data structure that works like an array but has a flexible size or length. The declaration syntax of the slice is similar to an array but size or length isn't needed.

//[]data type{values}
data := []int{1, 2, 3, 4, 5}
Enter fullscreen mode Exit fullscreen mode

The data that can be stored must have the same type like the array, the slice has a feature to select certain data that is needed.

Slice can be created using the make() method. This method takes 3 arguments. The first argument is the type of slice, the second is the length of slice and the last argument is the capacity of slice.
The example is like this:

//declare a slice of int using make method
//the length of slice is 3
//the capacity of slice is 6
data := make([]int, 3, 6)
//default items is based on data type's default value
// the default value of int is 0
//so this code print out [0 0 0]
fmt.Println("default items:", data)

//inserting some items
data[0] = 1
data[1] = 2
data[2] = 3
fmt.Println(data)
fmt.Println("Length of slice: ", len(data))
fmt.Println("Capacity of slice: ", cap(data))

data = append(data, 4, 5, 6) //insert new items

fmt.Println(data)
fmt.Println("Length of slice: ", len(data))
fmt.Println("Capacity of slice: ", cap(data))

//this insertion will increase the capacity 2x (two times)
data = append(data, 7)
fmt.Println("Length of slice: ", len(data))
fmt.Println("Capacity of slice: ", cap(data))
Enter fullscreen mode Exit fullscreen mode

Based on the code above. The length of the slice defines several items that are available in the slice. The capacity of a slice defines several items that can be inserted.

Create a slice with make function

If the length of the slice is larger than the capacity. The capacity will be increased 2 times if we use the make() method to create a slice.

Slice capacity update

The declaration of multiple dimension slice is also possible. For example, we can declare a 2D slice like this:

//declare a slice of []int
data := [][]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}
//retrieve the items from slice using nested loops
for _, v := range data {
    for _, val := range v {
        fmt.Print(val)
    }
    fmt.Println("")
}
Enter fullscreen mode Exit fullscreen mode

To select or slicing certain data from a slice. Use the syntax like this:

//name of the slice followed by [ start index : end index]
data[1:3]
Enter fullscreen mode Exit fullscreen mode

Here is an example, this code selects or slicing a certain value from a slice called "data" and stores it inside a new slice called "newData".

data := []int{8, 9, 10, 11, 12, 13}
//selecting certain data
//name of the slice followed by [ start index : end index]
newData := data[1:3]
fmt.Println(newData)
Enter fullscreen mode Exit fullscreen mode

The output :

[9 10]
Enter fullscreen mode Exit fullscreen mode

The mechanism of slicing a data from slice looks like this :
Slicing a slice

Another example of slicing a data in slice is like this :

  • data[1:] means select data or values from index 1 until the last index
  • data[:4] means select data from the first index until index 4. (data stored in index 4 isn't included).

Adding data into a slice can be done using the append() method. This method uses 2 inputs which is the slice and the data that need to be added. The usage is like this:

//declare a slice
cars := []string{"Nissan Skyline GT-R R34", "Honda NSX", "Toyota Supra", "Mazda RX-7"}
//append new data "Subaru Impreza WRX STI 22b"
cars = append(cars, "Subaru Impreza WRX STI 22b")
Enter fullscreen mode Exit fullscreen mode

The full example can be seen in this code:

//declare a slice
cars := []string{"Nissan Skyline GT-R R34", "Honda NSX", "Toyota Supra", "Mazda RX-7"}
fmt.Println("Before: ", cars)
//append new data "Subaru Impreza WRX STI 22b"
cars = append(cars, "Subaru Impreza WRX STI 22b")
fmt.Println("After: ", cars)
Enter fullscreen mode Exit fullscreen mode

Output:

Before:  [Nissan Skyline GT-R R34 Honda NSX Toyota Supra Mazda RX-7]
After:  [Nissan Skyline GT-R R34 Honda NSX Toyota Supra Mazda RX-7 Subaru Impreza WRX STI 22b]
Enter fullscreen mode Exit fullscreen mode

Removing certain data in a slice can be used also with append() with another approach. Let’s see the example:

Input: [12,13,14,15,16,17]
Output: [12,13,16,17] //remove 14 and 15
Enter fullscreen mode Exit fullscreen mode

Based on that case, the steps are :

  1. Take the first two items in slice {12,13}
  2. Append data from index 4 until the last index {16,17} to {12,13} The full example is like this:
data := []int{12, 13, 14, 15, 16, 17}
fmt.Println("Before removed: ", data)
/**
The steps are:
1. Take the first two items in slice {12, 13}
2. Append data from index 4 until the last index {16,17} to {12,13}
**/
data = append(data[:2], data[4:]...)
fmt.Println("After removed: ", data)
Enter fullscreen mode Exit fullscreen mode

The output:

Before removed:  [12 13 14 15 16 17]
After removed:  [12 13 16 17]
Enter fullscreen mode Exit fullscreen mode

Notice that there is a ... operator inside the append() method in the data[4:]... syntax. It means that all items are from a certain slice (this works like a spread operator in JavaScript). The ... operator is useful to use together with the append() function.
If ... isn't used, the syntax looks like this:

data = append(data[:2],16,17)
Enter fullscreen mode Exit fullscreen mode

Map

The map is a data structure with a key-value pairs mechanism. Each item must have a unique key. The map data structure is illustrated in the picture below.

Map Data Structure

The declaration of the map in Go looks like this:

//  map[key type]value type
data := map[string]string{
    "BMW":        "3.0 CSL",
    "Porsche":    "911",
    "Volkswagen": "Scirocco",
}
Enter fullscreen mode Exit fullscreen mode

To access or retrieve an item inside a map, we can use the key that is stored.

//access an item from "data" map with "BMW" as a key
data["BMW"]
Enter fullscreen mode Exit fullscreen mode

If we try to retrieve an item with a key that isn't found inside the map, the code doesn't throw any errors. Otherwise, return an empty item.
Adding a new item in the map can be done by defining a key and value that want to be stored inside the map. For example, we want to add a new item to the "data" map with a key and value specified like this:

data["RUF"] = "CTR"
Enter fullscreen mode Exit fullscreen mode

To retrieve all items from the map, there are 2 ways:

  • Using regular fmt.Println(). This way is suitable for checking the items that are stored inside the map.
  • Using for range loop. This way is suitable for doing any operation with items that are stored inside the map. Here is an example.
//declare a map with key type as a string and value type as a string
data := map[string]string{
    "BMW":        "3.0 CSL",
    "Porsche":    "911",
    "Volkswagen": "Scirocco",
}
//add new item into map
data["RUF"] = "CTR"
//retrieve all items from map with for range loop
for k, v := range data {
    fmt.Println("Brand: ", k, "\tCar Name: ", v)
}
Enter fullscreen mode Exit fullscreen mode

The output.

Brand:  BMW     Car Name:  3.0 CSL
Brand:  Porsche         Car Name:  911
Brand:  Volkswagen      Car Name:  Scirocco
Brand:  RUF     Car Name:  CTR
Enter fullscreen mode Exit fullscreen mode

Removing an item from the map can be done using the delete() method followed by the map name and item's key. The example is like this:

data := map[string]string{
    "BMW":        "3.0 CSL",
    "Porsche":    "911",
    "Volkswagen": "Scirocco",
}
//remove an item inside the map called "data" with key = "Volkswagen"
delete(data, "Volkswagen")
Enter fullscreen mode Exit fullscreen mode

The usage of multi-dimensional values is available in Map. The usage is like this:

//declare a map with string as a key that stored a value with type a slice of int
scores := map[string][]int{
    "Firstname":{9,8,9},
    "Lastname":{7,6,5},
    "Surname":{8,8,8}
}
Enter fullscreen mode Exit fullscreen mode

slices Package

In the recent Go version (1.21+), there is a package to work with the slice data structure called slices. This package provides many functionalities like binary search, reverse, deletion, and others.

In this post, some main functionalities will be covered. To learn more about the slices package, consider checking the full documentation.

Binary Search

Binary search is a search algorithm that can be used to search efficiently. Binary search only works with sorted elements. The slices package provides binary search functionality. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    numbers := []int{11, 23, 45, 67, 89}

    target := 67

    // perform binary search
    idx, isFound := slices.BinarySearch(numbers, target)

    fmt.Println("index: ", idx)
    fmt.Println("is found? ", isFound)
}

Enter fullscreen mode Exit fullscreen mode

Output

index:  3
is found?  true
Enter fullscreen mode Exit fullscreen mode

Based on the code above, the BinarySearch function returns two values, the index of the found element and the boolean value to indicate if the element is found.

Compact

The Compact function can be used to remove duplicates for sorted elements. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    // make sure to provide sorted elements
    foods := []string{"burger", "burger", "fried chicken", "lasagna", "lasagna"}

    // remove duplicates
    result := slices.Compact(foods)

    fmt.Println("the result: ", result)
}

Enter fullscreen mode Exit fullscreen mode

Output

the result:  [burger fried chicken lasagna]
Enter fullscreen mode Exit fullscreen mode

There is another function called CompactFunc to remove duplicates with a specific callback function. In this example, the duplicate elements are removed if two strings are equal while ignoring the case sensitivity.

package main

import (
    "fmt"
    "slices"
    "strings"
)

func main() {
    foods := []string{"burger", "Burger", "fried chicken", "fRIeD cHICkEN", "LASAGNA", "lasagna"}

    // remove duplicates
    foods = slices.CompactFunc(foods, func(food1, food2 string) bool {
        // check if two strings are equal and ignore the case sensitivity
        return strings.EqualFold(food1, food2)
    })

    fmt.Println("result: ", foods)
}

Enter fullscreen mode Exit fullscreen mode

Output

result:  [burger fried chicken LASAGNA]
Enter fullscreen mode Exit fullscreen mode

Contains

The Contains function can be used to check if a certain element exists inside a slice. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    numbers := []int{45, 12, 88, 25, 100}

    target := 25

    isExist := slices.Contains(numbers, target)

    if isExist {
        fmt.Println("the value is exist")
    } else {
        fmt.Println("nope")
    }
}

Enter fullscreen mode Exit fullscreen mode

Output

the value is exist
Enter fullscreen mode Exit fullscreen mode

The ContainsFunc is available to check if at least one element meets the condition based on the given callback function. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {

    numbers := []int{55, 67, 18, 19}

    // check if even number is exist
    // the even number checking is provided by the callback function
    isExist := slices.ContainsFunc(numbers, func(n int) bool {
        // check if number is even
        return n%2 == 0
    })

    if isExist {
        fmt.Println("even number found")
    } else {
        fmt.Println("even number not found")
    }
}

Enter fullscreen mode Exit fullscreen mode

Output

even number found
Enter fullscreen mode Exit fullscreen mode

Based on the code above, the ContainsFunc is used to check if at least one element is an even number. Each number is checked by using the callback function.

Delete

The Delete function is used to remove certain elements based on the given index at the start index and end index. This is an example of using the Delete function.

package main

import (
    "fmt"
    "slices"
)

func main() {
    items := []string{"milk", "coffee", "banana", "mango", "potato"}

    fmt.Println("before delete: ", items)

    items = slices.Delete(items, 2, 4)

    fmt.Println("after delete: ", items)
}

Enter fullscreen mode Exit fullscreen mode

Output

before delete:  [milk coffee banana mango potato]
after delete:  [milk coffee potato]
Enter fullscreen mode Exit fullscreen mode

Based on the code above, the Delete function deletes elements from index 2 up to index 3 (because index 4 is not included). This is an illustration of how the Delete function works.

Delete Function in slices

There is another function called DeleteFunc to remove or filter elements based on the given callback function. This is an example to remove the empty string.

package main

import (
    "fmt"
    "slices"
)

func main() {
    samples := []string{
        "",
        "this is good",
        "this is fine",
        "",
        "",
        "not bad at all",
    }

    // remove empty strings
    samples = slices.DeleteFunc(samples, func(sample string) bool {
        // check if string is empty
        return sample == ""
    })

    for _, s := range samples {
        fmt.Println(s)
    }
}


Enter fullscreen mode Exit fullscreen mode

Output

this is good
this is fine
not bad at all
Enter fullscreen mode Exit fullscreen mode

Insert

The Insert function is used to insert a new element in the specific index location. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    items := []string{"biscuit", "cereal", "waffle"}

    // insert single item
    items = slices.Insert(items, 1, "pancake")

    // insert many items
    otherItems := []string{"milk", "oat milk"}
    items = slices.Insert(items, 2, otherItems...)

    fmt.Println("result: ", items)
}

Enter fullscreen mode Exit fullscreen mode

Output

result:  [biscuit pancake milk oat milk cereal waffle]
Enter fullscreen mode Exit fullscreen mode

Based on the code above, the Insert function can be used to insert a single or many elements in a slice in the specific index location. This picture describes how the Insert function works.

Insert single item

Insert multiple items

Max and Min

The Max function can be used to get the maximum value in a slice. The Min function can be used to get the minimum value in a slice. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    numbers := []int{44, -50, 1, 300, 2, 78}

    maxNum := slices.Max(numbers)
    minNum := slices.Min(numbers)

    fmt.Println("max number :", maxNum)
    fmt.Println("min number :", minNum)
}

Enter fullscreen mode Exit fullscreen mode

Output

max number : 300
min number : -50
Enter fullscreen mode Exit fullscreen mode

Reverse

The Reverse function can be used to reverse elements in a slice. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    items := []string{"tea", "milk", "cereal"}

    fmt.Println("original: ", items)

    slices.Reverse(items)

    fmt.Println("after reversed: ", items)
}

Enter fullscreen mode Exit fullscreen mode

Output

original:  [tea milk cereal]
after reversed:  [cereal milk tea]
Enter fullscreen mode Exit fullscreen mode

Another example is reversing a word.

package main

import (
    "fmt"
    "slices"
)

func main() {
    word := "quick"

    // get the letters from a word by converting it to the []byte
    // because string is a slice of bytes
    letters := []byte(word)

    // reverse a word
    slices.Reverse(letters)

    reversed := string(letters)

    fmt.Println("reversed word: ", reversed)
}

Enter fullscreen mode Exit fullscreen mode

Output

reversed word:  kciuq
Enter fullscreen mode Exit fullscreen mode

Sort

The Sort function can be used to perform a sorting mechanism in a slice. This is an example.

package main

import (
    "fmt"
    "slices"
)

func main() {
    numbers := []int{99, 34, 45, 10, -1, 3}

    slices.Sort(numbers)

    fmt.Println("after sorted: ", numbers)
}

Enter fullscreen mode Exit fullscreen mode

Output

after sorted:  [-1 3 10 34 45 99]
Enter fullscreen mode Exit fullscreen mode

The SortFunc can be used to sort elements in a specific order (ascending or descending order). This is an example of SortFunc usage to sort elements in descending order.

package main

import (
    "cmp"
    "fmt"
    "slices"
)

func main() {
    numbers := []int{99, 34, 45, 10, -1, 3}

    // sort in descending order
    slices.SortFunc(numbers, func(a, b int) int {
        return cmp.Compare(b, a)
    })

    fmt.Println("after sorted: ", numbers)
}

Enter fullscreen mode Exit fullscreen mode

Output

after sorted:  [99 45 34 10 3 -1]
Enter fullscreen mode Exit fullscreen mode

maps Package

In the recent Go version (1.21+), there is a package to work with the map data structure called maps. This package provides many functionalities like filter and equality check.

In this post, some main functionalities will be covered. To learn more about the maps package, consider to check the full documentation.

Delete

The DeleteFunc can be used to delete an item inside the map with a callback function. This is an example of using DeleteFunc to delete an item with a score value of less than 70.

package main

import (
    "fmt"
    "maps"
)

func main() {
    scores := map[string]int{
        "bob":  79,
        "alex": 60,
        "john": 88,
        "doe":  65,
    }

    maps.DeleteFunc(scores, func(name string, score int) bool {
        return score < 70
    })

    fmt.Println("after deleted: ", scores)
}

Enter fullscreen mode Exit fullscreen mode

Output

after deleted:  map[bob:79 john:88]
Enter fullscreen mode Exit fullscreen mode

Equal

The Equal function is used to check the equality of the two maps. The two maps are equal if the key and value are both equals.

package main

import (
    "fmt"
    "maps"
)

func main() {
    c1 := map[string]string{
        "C1": "Algorithm",
        "C2": "Data Science",
    }

    c2 := map[string]string{
        "c1": "Algorithm",
        "c2": "Data Science",
    }

    isEqual := maps.Equal(c1, c2)

    fmt.Println("is equal: ", isEqual)
}

Enter fullscreen mode Exit fullscreen mode

Output

is equal:  false
Enter fullscreen mode Exit fullscreen mode

Based on the code above, the Equal returns false because the first map (c1) has uppercase characters for the key whilst the second map (c2) has lowercase characters for the key.

The EqualFunc can be used to perform an equality check with an additional callback function. The callback function is used for checking the value of the map. This is an example.

package main

import (
    "fmt"
    "maps"
    "strings"
)

func main() {
    c1 := map[string]string{
        "C1": "Algorithm",
        "C2": "Data Science",
    }

    c2 := map[string]string{
        "C1": "aLgOrITHM",
        "C2": "DATA SCIENCE",
    }

    // check the equality with callback function to check the value
    isEqual := maps.EqualFunc(c1, c2, func(item1, item2 string) bool {
        return strings.EqualFold(item1, item2)
    })

    fmt.Println("is equal: ", isEqual)
}

Enter fullscreen mode Exit fullscreen mode

Output

is equal:  true
Enter fullscreen mode Exit fullscreen mode

Based on the code above, the EqualFunc returns true because there is an additional callback function to ensure two values are equal.

Notes

  • Array is rarely used in Go programming. The array can be used to store multiple values with the exact size. So the size wouldn't change.
  • Slice is commonly used in Go programming. Slice can be used to store multiple values or items with enhanced features like selecting certain data.
  • Map is also commonly used. The map can be used to store multiple values with specified keys.
  • Learn more about the slices package here *Learn more about the maps package here
  • Learn more about Array, Slice, and Map here

I hope this article helps to learn the Go programming language. If you have any thoughts or feedback, you can write it in the discussion section below.

Top comments (0)