How to add elements to a Slice
To add elements to the existing slice, append()
function needs to be used. It requires at least two parameters. Slice variable and an element that we want to add to the slice. We can also add multiple elements at once.
var list []int
fmt.Println(list) // []
list = append(list, 9)
fmt.Println(list) // [9]
list = append(list, 8, 7, 6)
fmt.Println(list) // [9 8 7 6]
Add elements in a funciton
Let’s try to add elements in a function. I defined the following two functions for comparison.
func addNineByValue(array []int) {
// this value of array is never used (SA4006)go-staticcheck
array = append(array, 9)
}
func addNineByPointer(array *[]int) {
*array = append(*array, 9)
}
The parameter is a value or pointer. A warning is shown in the first function. Let’s see the result below.
fmt.Println(list) // [9 8 7 6]
addNineByValue(list)
fmt.Println(list) // [9 8 7 6]
addNineByPointer(&list)
fmt.Println(list) // [9 8 7 6 9]
The slice is not updated with the first function because it adds an element to copied slice. However, the second one updates the slice because it adds it to the original slice.
The second way is called a destructive function which changes the original data. This is a bad practice because the value is updated under the hood. It can cause a bug because the value is updated without knowing it.
The first way is the same as append()
function. Of course, the value must be returned and the returned value must be assigned to the original variable. Stick to this way to avoid unnecessary bug!!
How to clear a Slice
In case we need to clear a slice, we can simply assign nil to the slice.
fmt.Println(list) // [9 8 7 6 9]
list = nil
fmt.Println(list) // []
fmt.Println(len(list)) // 0
Assigning nil
perfectly works.
How to add the existing Slice to a Slice with three dots
The existing slice can be added to a slice with append()
function. In this case, three dots need to be added to the slice variable. It expands the slice into a parameter list. It means that the elements are passed to the function.
mySlice := []int{9, 8, 7, 6}
list = append(list, mySlice...)
fmt.Println(list) // [9 8 7 6]
If the existing slice is empty or nil, no element is added. This is safe.
fmt.Println(list) // []
var emptyList []int
list = append(list, emptyList...)
fmt.Println(list) // []
How to add an array to a Slice
The three dots can’t be used for an array. See the following example.
myArray := [4]int{5, 4, 3, 2}
// cannot use myArray (variable of type [4]int) as []int value in argument to appendcompilerIncompatibleAssign
list = append(list, myArray...)
The array needs to be converted to a slice in this case. An array can be converted by using a colon.
myArray := [4]int{5, 4, 3, 2}
list = append(list, myArray[:]...)
fmt.Println(list) // [5 4 3 2]
fmt.Println(reflect.TypeOf(myArray).Kind()) // array
fmt.Println(reflect.TypeOf(myArray[:]).Kind()) // slice
The type of the original value is array but it was converted to slice with the colon.
What’s this colon? Let’s go dive into it in the next chapter.
Slice with colon to get ranged elements
By using a colon, we can easily extract ranged elements from a slice.
list = append(list, 1, 2, 3, 4)
fmt.Println(list) // [1 2 3 4]
fmt.Println(list[:]) // [1 2 3 4]
fmt.Println(list[0:]) // [1 2 3 4]
fmt.Println(list[0:1]) // [1]
fmt.Println(list[0:2]) // [1 2]
fmt.Println(list[2:3]) // [3]
fmt.Println(list[2:2]) // []
fmt.Println(list[2:4]) // [3 4]
fmt.Println(list[:3]) // [1 2 3]
The number is index of the element. It extracts values between the start index and the end index but the value of the end index is not included.
The end index must be the same or bigger than the start index. Likewise, the index value must be 0 or positive value as you can see in the following example.
// invalid slice indices: 2 < 4
fmt.Println(list[4:2])
// invalid argument: index -3 (constant of type int) must not be negative
fmt.Println(list[:-3])
Utility functions for Slice
Golang doesn’t have useful utility functions because they can easily be implemented. Let’s define the functions here.
We need to know Generics to implement them. Check the following post if you are not familiar with Generics.
The slice has the following values in the following code.
fmt.Println(list) // [1 2 3 4]
How to filter Slice
We use any
, a.k.a interface
, to support all types. The filtering condition must be defined on the caller side and the function must return a boolean.
func Filter[T any](array []T, callback func(T) bool) []T {
result := []T{}
for _, value := range array {
if callback(value) {
result = append(result, value)
}
}
return result
}
// [3 4]
fmt.Println(Filter(list, func(value int) bool { return value > 2 }))
// []
fmt.Println(Filter(list, func(value int) bool { return value > 4 }))
How to Select/Map data
If we need to do something with the elements and want to have the result in a variable, this function is useful. This is the same as map()
in JavaScript/Python, Select()
in C#.
It needs to have two types T and K. T is a slice type. K is returned value type. This is because int value might need to be converted to a string. To address these cases, a different type needs to be defined.
func Select[T any, K any](array []T, callback func(T) K) []K {
result := make([]K, len(array))
for index, value := range array {
result[index] = callback(value)
}
return result
}
// [1 4 9 16]
fmt.Println(Select(list, func(value int) int { return value * value }))
// [result: 1 result: 4 result: 9 result: 16]
fmt.Println(Select(list, func(value int) string { return fmt.Sprintf("result: %d", value*value) }))
How to check if a Slice contains a value
It’s similar to Filter()
. It returns a boolean if it finds an element.
func Some[T any](array []T, callback func(T) bool) bool {
for _, value := range array {
if callback(value) {
return true
}
}
return false
}
fmt.Println(Some(list, func(value int) bool { return value < 2 })) // true
fmt.Println(Some(list, func(value int) bool { return value > 4 })) // false
fmt.Println(Some(list, func(value int) bool { return value == 4 })) // true
If we want to put the value directly, we can implement it in the following way.
func Contains[T comparable](array []T, searchValue T) bool {
for _, value := range array {
if value == searchValue {
return true
}
}
return false
}
fmt.Println(Contains(list, 2)) // true
fmt.Println(Contains(list, 4)) // true
fmt.Println(Contains(list, 5)) // false
We can’t use any type for this function because some types aren’t comparable for example map.
How to extract the first element that matches a condition
To get the first element that matches the condition, we can implement it in the following way.
func Find[T any](array []T, callback func(T) bool) *T {
for _, value := range array {
if callback(value) {
return &value
}
}
return nil
}
fmt.Println(*Find(list, func(value int) bool { return value < 2 })) // 1
fmt.Println(Find(list, func(value int) bool { return value > 4 })) // <nil>
fmt.Println(*Find(list, func(value int) bool { return value == 4 })) // 4
But I think it’s better to follow Golang way. Let’s add the second return value to know whether it finds an element. It’s better to use copied value instead of a pointer.
func Find[T any](array []T, callback func(T) bool) (T, bool) {
for _, value := range array {
if callback(value) {
return value, true
}
}
var zeroValue T
return zeroValue, false
}
value, ok := Find2(list, func(value int) bool { return value < 2 })
fmt.Printf("value: %v, ok: %t\n", value, ok) // value: 1, ok: true
value, ok = Find2(list, func(value int) bool { return value == 5 })
fmt.Printf("value: %v, ok: %t\n", value, ok) // value: 0, ok: false
zeroValue
returns a default value of the type. In this example above, it’s 0 because it’s int type.
If the data type is map type, the result becomes as follows.
map1 := map[string]int{"first": 1, "second": 2}
map2 := map[string]int{"three": 3, "four": 4}
valueMap, ok := Find2([]map[string]int{map1, map2}, func(valueMap map[string]int) bool {
_, ok := valueMap["first"]
return ok
})
fmt.Printf("value: %v, ok: %t\n", valueMap, ok) // value: map[first:1 second:2], ok: true
valueMap, ok = Find2([]map[string]int{map1, map2}, func(valueMap map[string]int) bool {
_, ok := valueMap["notExist"]
return ok
})
fmt.Printf("value: %v, ok: %t\n", valueMap, ok) // value: map[], ok: false
Comments