Slices in Go

This is a study note from YouTube Go tutorial https://www.youtube.com/watch?v=YS4e4q9oBaU
Slice is used in most of cases in Go instead of arrays because it is dynamic. This means you can increase the capacity of slice on the runtime. You can create a slice like below:

Slice creation using []int


a := []int{1, 2, 3}

It is very similar to array creation except no size in the square bracket "[]".

Unlike array is a value type, slice is a reference type like list in Java. So when you create a slice and then assign it to another slice variable, both of them refer to the same memory.
package main

import (
"fmt"
)

func main() {

a := []int{1, 2, 3}
b := a
b[1] = 5
fmt.Println(a)
fmt.Println(b)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))
}

Result:
[1 5 3] [1 5 3] Length: 3 Capacity: 3

From the result, you can see both a and b has the same values even though only b was changed.

Slice operations


There are different ways to create slices.
func main() {

a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
b := a[:] // slice of all elements
c := a[3:] // slice from 4th element to end
d := a[:6] // slice first 6 elements
e := a[3:6] // slice the 4th, 5th and 6th elements

fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
fmt.Println(e)

}
The result is:
[1 2 3 4 5 6 7 8 9 10] [1 2 3 4 5 6 7 8 9 10] [4 5 6 7 8 9 10] [1 2 3 4 5 6] [4 5 6]

To make easier to remember, if we represent the creation operation using format "[left:right]", it will get the data ranging from left to right. Value in left is inclusive while value in right is exclusive.

Here I emphasise again that slice is a reference type. No matter what above methods you use, all slice still refer to the same memory. Once you change value in one slice, it will be changed in all other slices.
For example:
func main() {

a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
b := a[:] // slice of all elements
c := a[3:] // slice from 4th element to end
d := a[:6] // slice first 6 elements
e := a[3:6] // slice the 4th, 5th and 6th elements

e[1] = 0 // Here modify the 2nd value to be 0
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
fmt.Println(e)
}
The result is:
[1 2 3 4 0 6 7 8 9 10] [1 2 3 4 0 6 7 8 9 10] [4 0 6 7 8 9 10] [1 2 3 4 0 6] [4 0 6]

The value in the same memory in different slices is changed as well.

Slice creation using make function


You can also use "make" function to create a slice. The make built-in function allocates and initialises an object of type slice, map, or chan(only). 

Make function can have two parameters or three parameters.

Two-parameter make function prototype is like below:
make(type, length)

func main() {

a := make([]int, 3)
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))

}
The result is:
[0 0 0] Length: 3 Capacity: 3

Three-parameter make function prototype is:
make(type, length, capacity)

func main() {

a := make([]int, 3, 100)
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))

}
The result is:
[0 0 0] Length: 3 Capacity: 100

Add elements in slice using append function

func main() {

a := []int{}
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))
a = append(a, 1)
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))
a = append(a, 2, 3, 4, 5)
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))
}
The result is:
[] Length: 0 Capacity: 0 [1]
Length: 1
Capacity: 1
[1 2 3 4 5] Length: 5 Capacity: 6

Append a slice to a slice

If you want to append a slice to another slice, you cannot do so by just using a = append(a, b) because append function only accepts the same type as the slice element - it cannot accept slice as an input.

However you can use spread operator to archive this - a = append(a, b...). It is same as a = append(a, 2, 3, 4, 5)

func main() {

a := []int{1}
b := []int{2, 3, 4, 5}
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))
a = append(a, b...)
fmt.Println(a)
fmt.Printf("Length: %v\n", len(a))
fmt.Printf("Capacity: %v\n", cap(a))

}
The result is:
[1] Length: 1 Capacity: 1
[1 2 3 4 5] Length: 5 Capacity: 6

Remove operation in slice

To remove the first element.
func main() {

a := []int{1, 2, 3, 4, 5}
b := a[1:]
fmt.Println(b)
}
The result is:
[2 3 4 5]

To remove the last element.
func main() {

a := []int{1, 2, 3, 4, 5}
b := a[:len(a)-1]
fmt.Println(b)
}

The result is:
[1 2 3 4]

To remove the element in the middle.
func main() {

a := []int{1, 2, 3, 4, 5}
b := append(a[:2], a[3:]...)
fmt.Println(b)
}
The result is:
[1 2 4 5]

However, as mentioned before, slice is a reference type. When you modify a slice, it will impact all other slice variables that refer to it.
When you do fmt.Println(a), you will get result like below:
[1 2 4 5 5]

From above you can see that remove operation impact the original slice a as well. So be aware of this!



Comments

Popular posts from this blog

Basic understanding of TLS-PSK protocol

Differences between ASIC, ASSP and ASIP

Orthogonal instruction set