Golang Slices and Arrays
Golang is an open source programming language used largely for server-side programming and is developed by Google.
With it static typing, it is a very simple and versatile programming language that is an excellent choice for beginners. Golang is a type-safe language and has a flexible and powerful type system.
In addition, its syntax is very simple, making your code easier to read. It is a derivative of C and some features taken from other languages such as garbage collection (from Java) and few simple dynamic typing.
Arrays and slices in data structure are the key ingredients to any Go program. These are the main building blocks from which complex software is built. Having a good understanding of these foundation concepts is crucial when designing software.
Even though arrays seem simple, when it comes to usage you will have many questions.
- Is it fixed or variable in size?
- Does the size determine the type?
- Do multidimensional arrays have any specific characteristics?
- And what about empty arrays?
In order to respond to the above questions, the Go development team introduced Slices, which are dynamic-sized arrays and give a flexible, extensible data structure.
We will cover the following sections in this article.
-
iii) Creating a Slice using built-in make() function
iv) Slice of Slices
#1 Arrays in Golang
Arrays in Golang are simply sequences or homogeneous collections of elements with similar data types in the memory.
The values of the array are called elements or items. Arrays can contain zero or more than zero values.
An array element can be accessed by an index.
Consider a scenario where we have numbers up to 100 and we want to store them in a variable.
In contrast to storing them individually as num0, num1 till num99, storing them in an array called num and accessing each number as num[0], num[1], etc. is much more straightforward.
i) Array Declaration
In Go, arrays can be declared using two methods.
- Using var keyword
- Using := sign (Shorthand Declaration Method)
a) Using var keyword:
var array_name = [length]datatype{values} // here length is defined
or
var array_name = [...]datatype{values} // here length is inferred
b) Using := sign:
array_name := [length]datatype{values} // here length is defined
or
array_name := [...]datatype{values} // here length is inferred
Golang Array Examples:
Output:
ii) Accessing Go array
Accessing array elements is very much easier since the memory is laid out in sequence. To access the elements we can use [] operator.
// Declare an integer array of five elements.
// Initialize each element with a specific value.
array := [5]int{10, 20, 30, 40, 50}
// Change the value at index 2.
array[4] = 55
iii) Multidimensional Array
Multidimensional array ia an array with more than 2 dimensions which are represented by rows and columns.
General form of a multidimensional array declaration:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
//example
var threedim [5][10][4]int
Example of 2-D array:
// Declare a two dimensional integer array of four elements
// by two elements.
var array [4][2]int
// Use an array literal to declare and initialize a two
// dimensional integer array.
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// Declare and initialize index 1 and 3 of the outer array.
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
// Declare and initialize individual elements of the outer
// and inner array.
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
Accessing elements of a two-dimensional array:
// Declare a two dimensional integer array of two elements.
var array [2][2]int
// Set integer values to each individual element.
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
array[1][1] = 40
You can copy the multidimensional array to another array when they have the same datatype.
Assigning multidimensional arrays of the same type:
// Declare two different two dimensional integer arrays.
var array1 [2][2]int
var array2 [2][2]int
// Add integer values to each individual element.
array2[0][0] = 10
array2[0][1] = 20
array2[1][0] = 30
array2[1][1] = 40
// Copy the values from array2 into array1.
array1 = array2
Since array is an value you can also copy the individual dimensions.
// Copy index 1 of array1 into a new array of the same type.
var array3 [2]int = array1[1]
// Copy the integer found in index 1 of the outer array
// and index 0 of the interior array into a new variable of
// type integer.
var value int = array1[1][0]
Example of multidimensional array:
package main
import ( "fmt")
func printarray(a [3][2]string) {
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
func main() {
companies := [3][2]string{
{"apple", "microsoft"},
{"meta", "netflix"},
{"atatus", "amazon"},
}
printarray(companies)
var monitoring_tools [3][2]string
monitoring_tools[0][0] = "apm"
monitoring_tools[0][1] = "rum"
monitoring_tools[1][0] = "synthetics"
monitoring_tools[1][1] = "logs"
monitoring_tools[2][0] = "api analytics"
monitoring_tools[2][1] = "error monitoring"
fmt.Printf("\n")
printarray(monitoring_tools)
}
Output:
iv) Passing arrays between functions
An array passed between functions might take too much memory and performance because variables are always passed by value when they are passed between functions, whereas when the variable is passed as an array, no matter what its size is, it is copied.
// Declare an array
var array []int
// Pass the array to the function foo.
foo(array)
func foo(array []int) {
...
return array
}
v) Points to be remembered while working with Go array
The arrays in Golang are mutable, which means that the values can easily be altered with the syntax detailed below.
var array_name[index] = element
Example:
package main
import ("fmt")
func main() {
var monitoring_tools = [3]string{"apm", "rum", "infra"}
var monitoring_tools[1] = "synthetic"
fmt.Println(monitoring_tools)
}
- Array elements can be accessed using index value or by for loop.
Example:
package main
import ("fmt")
func main() {
var monitoring_tools = [3]string{"apm", "rum", "infra"}
// Using index value
fmt.Println(monitoring_tools[1])
// Using for loop
for i:= 0; i < len(monitoring_tools); i++{
fmt.Println(monitoring_tools[i])
}
}
- Duplicate elements can be stored in an array.
- Arrays in Go are generally one-dimensional.
- In Golang, array is of value type not reference type.
Example:
package main
import ("fmt")
func main() {
// Creating an array with inferred array length
tools:= [...]string{"apm", "rum", "synthetic", "infra", "logs"}
fmt.Println("Original tools array(Before):", tools)
// Creating a new variable and initialize with tools
monitoring_tools := tools
fmt.Println("New monitoring tools array(before):", monitoring_tools)
// Change the value at index 0 to application performance monitoring
monitoring_tools[0] = "application performance monitoring"
fmt.Println("New monitoring tools array(After):", monitoring_tools)
fmt.Println("Original tools array(After):", tools)
}
- The array's type is also comparable when its elements are compared using the == operator.
Go programs tend not to use arrays due to their fixed size that limits their expressive power. Fortunately, slices can help in this regard. Scroll down to know more about Slices.
#2 Slices in Golang
Slices are a lightweight and variable-length sequence Go data structure that is more powerful, flexible and convenient than arrays.
The slices also support storing multiple elements of the same type in a single variable, just as arrays do. Slices, on the other hand, permit you to change the length whenever you like.
The problem with raw arrays is that the size can't be changed once created, the element sizes are fixed.
For example, imagine you have an array that contains addresses for each person in your company. You can't change the addresses in the array after you have added a new person to the company, instead you need to add a new person to the array.
To use Slices effectively, you need to understand what they are and what they do.
i) Creating a Slice
Slices are declared like arrays, but without specifying what size they are. The size can therefore be changed as necessary.
The type T of a slice is represented as []T.
You can create slice using three methods.
- Creating slice using literals.
- Creating a slice from an array.
- Creating a slice from another slice.
a) Creating a slice using literals
As with arrays, slice literals can be declared using the var keyword without mentioning the size in the square brackets.
// Creating a slice using a slice literal
var num = []int{1, 2, 3, 4, 5}
Complete example of creating slice with literals.
package main
import ("fmt")
func main() {
// Creating a slice using a slice literal
var odd_num = []int{3, 5, 7, 9, 11, 13, 17}
// Usng := sign
even_num := []int{2, 4, 8, 16, 32, 64}
fmt.Println("Odd numbers are = ", odd_num)
fmt.Println("Even numbers are = ", even_num)
}
Output:
b) Creating a slice using arrays
Slices are segments of an array, so they can be declared by using a lower bound(low) and an upper bound(up). This is done by separating the two values with a colon.
arr[low:up]
Example:
package main
import ("fmt")
func main() {
// Initializing an array
var odd_num = [7]int{3, 5, 7, 9, 11, 13, 17}
// Creating a slice from the array
var odd_slice []int = odd_num[1:4]
fmt.Println("Array of odd numbers = ", odd_num)
fmt.Println("Slice = ", odd_slice)
}
Output:
The default value of indices low and high are 0 and length of the slice respectively.
Below is an example of a slice with strings.
package main
import ("fmt")
func main() {
monitoring := [5]string{"APM", "RUM", "Synthetics", "Infra", "Logs"}
slice1 := monitoring[1:4]
slice2 := monitoring[:3]
slice3 := monitoring[2:]
slice4 := monitoring[:]
fmt.Println("Array = ", monitoring)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
fmt.Println("slice3 = ", slice3)
fmt.Println("slice4 = ", slice4)
}
c) Creating a slice from another slice
The existing slice can be sliced to create a new slice.
Example:
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:
ii) Components in a Slice
Slice consists of three elements:
- Pointers - They are special variables in slice that are used to store the memory address of another variable. This is used to point a particular elements in an array.
- Length - It is the total number of elements present in an array.
- Capacity - It determines the amount of size upto which an array can expand or shrink.
Examples of components:
var a = [6]int{100, 200, 300, 400, 500, 600}
var s = [1:4]
The variable s is created from the indices 1 to 4. Therefore the length of a is 6 and b is 5 and the capacity of a is 6 and b is 5 since the variable s has been created from the variable a of index 1.
Example using string:
// Golang program to illustrate
// the working of the slice components
package main
import ("fmt")
func main() {
// Creating an array
arr := [7]string{"Atatus", "offers", "full-stack", "monitoring",
"tools"}
// Display array
fmt.Println("Array:", arr)
// Creating a slice
myslice := arr[0:6]
// Display slice
fmt.Println("Slice:", myslice)
// Display length of the slice
fmt.Printf("Length of the slice: %d", len(myslice))
// Display the capacity of the slice
fmt.Printf("\nCapacity of the slice: %d", cap(myslice))
}
The length of the slice can be extended up to its capacity when re-slicing. A slice that exceeds its length will trigger an runtime error.
Below given is an example to understand the re-slicing concept.
package main
import ("fmt")
func main() {
s := []int{2, 7, 3, 9, 10, 56, 78, 23, 89, 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("\nSlicing index 2 through 8")
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 removing the first two elements")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
iii) Creating a Slice using built-in make() function
Golang also offers a built-in function called make() that can be used to create slices.
func make([]T, len, cap) []T
The make function takes three parameters: a type, a length, and an optional capacity. Slices refer to an array with an underlying capacity equal to the given size.
Example:
slice := make([]string, 2, 3)
An example of creating a slice with make() function.
package main
import ("fmt")
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
The values of the slice created using the make() function will be [0 0 0 0 0] respectively.
iv) Slice of Slices
These are just nested slices. Various types of slices can be created, which can contain slices inside slices using one single slice.
package main
import ("fmt")
func main() {
monitoring_tools := [][]string{
{"rum", "apm"},
{"synthetic", "logs"},
{"analytics", "infra"},
}
fmt.Println("Slice monitoring_tools = ", monitoring_tools)
fmt.Println("length = ", len(monitoring_tools))
fmt.Println("capacity = ", cap(monitoring_tools))
}
v) Iterate over Slice
The process of developing an application or undertaking any task generally involves iteration to accomplish something in collection.
Golang slices can be iterated in either of the following two ways:
a.) Using a for...range loop
package main
import ("fmt")
func main() {
numbers := []int{1, 13, 120, 345, 1902}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
The index can be initialized with only one variable if the value is not necessary.
for index := range numbers {
fmt.Printf("Indexes are: %d", index)
}
If you only need the value, you can initialize it as follows:
package main
import ("fmt")
func main() {
numbers := []float64{2.5, 7.3, 14.8, 54.4}
sum := 0.0
for _, number := range numbers {
sum += number
}
fmt.Printf("Total Sum = %.2f\n", sum)
}
b.) Using a for loop
package main
import ("fmt")
func main() {
countries := []string{"India", "America", "Russia", "England"}
for i := 0; i < len(countries); i++ {
fmt.Println(countries[i])
}
}
vi) Functions in Slice
- Copy
- Append
- Remove
Slice Copy
With the built-in function copy() in Go you can easily copy the source slice into the destination slice. To copy the slice to another slice without affecting the original, the copy function is used.
Example:
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(s1))
copy(slice2, slice1)
fmt.Println(slice1, slice2) // [1 2 3 4 5] [1 2 3 4 5]
slice2[1] = 10 // changing s2 does not affect s1
fmt.Println(slice1, slice2) // [1 2 3 4 5] [1 10 3 4 5]
}
When using the copy function, the first argument is the destination slice, and the second argument is the source slice. Both slices should have the same datatype.
During the copying of slices, the length of both slices may or may not be equal. Copying to an Nil slice will result in nothing being copied since it will only copy the smallest number of elements.
Example:
func main() {
s1 := []int{1, 2, 3, 4, 5} // s1 is length 5
s2 := make([]int, 3) // s2 is length 3
copy(s2, s1)
fmt.Println(s2) // [1 2 3]
var s3 []int // nil slice of length 0
copy(s3, s1)
fmt.Println(s3) // []
}
Slice Append
As we all know, it is not possible to change or increase the length of an array, but in slices, it is dynamic and we can add additional elements in existing slices.p
To add or append a slice at the end of another slice you can use append() function in Go.
slice = append(slice, elem1, elem2, ...)
Slices can be appended directly to each other by using the ... operator. In append() function, slice is the first parameter and the next parameter can be either one or more values to be appended.
package main
import ("fmt")
func main() {
slice1 := []string{"Kiwi", "Orange", "Mango"}
slice2 := []string{"Berry", "Cherry", "Grapes"}
slice3 := append(slice1, slice2...)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
fmt.Println("After appending slice1 & slice2 = ", slice3)
}
Slice Remove
Go doesn't provide a built-in function for removing an element in a slice. There are few strategies to remove the element.
Example #1 - If the slice order is important
When we want to preserve the order of slices after removing an element, we move the elements to the left of the deleted element by one.
package main
import ("fmt")
func remove(slice []int, s int) []int {
return append(slice[:s], slice[s+1:]...)
}
func main() {
var Slice1 = []int{1, 2, 3, 4, 5}
fmt.Printf("slice1: %v\n", Slice1)
Slice2 := remove(Slice1, 2)
fmt.Printf("slice2: %v\n", Slice2)
}
Output:
Example #2 - If the slice order is not important
package main
import ("fmt")
func remove(s []int, i int) []int {
s[i] = s[len(s)-1]
return s[:len(s)-1]
}
func main() {
var Slice1 = []int{1, 2, 3, 4, 5}
fmt.Printf("slice1: %v\n", Slice1)
Slice2 := remove(Slice1, 2)
fmt.Printf("slice2: %v\n", Slice2)
}
Output:
Example #3 - Remove elements from a slice while iterating
Multiple elements can be removed from a slice by iterating over it. You must, however, iterate from the end to the beginning, rather than the other way around.
It is because the slice length keeps decreasing when an element is removed that you will experience the index out of bounds error if you start iterating from the start.
package main
import ("fmt")
func main() {
num := []int{1, 2, 3, 4, 5, 6}
for i := len(num) - 1; i >= 0; i-- {
// this checks whether the integer is even
// before removing it from the slice
if num[i]%2 == 0 {
num = append(num[:i], num[i+1:]...)
}
}
fmt.Println(num) // [1,3,5]
}
Output:
vii) Passing Slice between functions
The pointer variable within a slice's argument will refer to the same underlying array, even if the slice is passed by value. So, when a slice is passed to a function, changes made inside the function are visible outside it as well.
package main
import ("fmt")
// A function called change in which slice
// is passed by value
func change(element []string) {
// Modifying the given slice
element[2] = "api analytics"
fmt.Println("Modified slice: ", element)
}
// Main function
func main() {
// Creating slice
monitoring_tools := []string{"apm", "rum", "logs", "synthetics"}
fmt.Println("Initial slice: ", monitoring_tools)
// Passing the slice to the function
change(monitoring_tools)
fmt.Println("Final slice:", monitoring_tools)
}
Output:
No matter how many functions they pass in, the slice pointer always points to the same reference.
For example, when we change the value logs to api analytics at index value 2, the slice pointer points to reference 2. After this change, the slice outside the function is also reflected, so the final slice is [apm rum api analytics synthetics].
Conclusion
In summary, slices are an easy way for programmers to create dynamic structures. They also allow programmers to create very efficient structures.
Following are a few considerations when using arrays and slices.
- Arrays are used when the entity is represented by a collection of non-empty items.
- To describe a general collection that you can add to or remove elements from, you should consider slices.
- You can define slices to describe collections containing an unlimited number of elements.
- Does the collection need to be modified in any way? If so, use slices.
You now have a solid understanding of how slices and arrays work in Go.
Slices become much easy to use once you gain an understanding of how they work, especially with the built-in functions for copy and append.
Go slice was created as an alternative to arrays, but it can be used as an alternative to pointers, and as a simple way to make it easier for novice programmers to manage memory.