DEV Community

Thành Đoàn Nhật
Thành Đoàn Nhật

Posted on

Golang Day 2: Arrays, Slices and Maps

Today, I learned about various types of collections in Go, because processing collection of data is one of the most common tasks in programing, such as data from files, from response API or from anything.
Since this is my current understanding, so if I have any mistake or you have additional insights, please leave a comment, I would really appreciate it.

Arrays

Overall

  • Arrays form the base data structure for both slices and maps.
  • An array is a fix-length data type that contains a contiguous block of elements of the same type. This could be a built-in type such as integer and strings, or it can be a struct type. Because of this, the data of memory can stay in CPU caches longer, and we can retrieve data in array quickly. Since all elements in arrays have the same type and size, Go can calculate memory address of any elements directly from its index. This allow constant-time access (O(1)). #### Declaring and initializing
  • Array declare by specifying the type of data to be stored and total number of elements required, known as length of array.
// Declare integer array with length is 5
var arr [5]int
Enter fullscreen mode Exit fullscreen mode
  • We can't modify both type of element which stored in array and length of array after declare, so if we want to store in longer array, we need to create new array with target length, and copy all elements from old array to it.
  • We can declare array by literal, and this is a idiomatic in Go.
// Declare an integer array of 5 elements
array := [5]int{1, 2, 3, 4, 5}
* Note: we cannot declare an array of type _int_ and initialize it with _string_ values, it raise error when editor time.
// Declare an integer array of 5 elements but not specific number elements, Go automatically get that info from list value initializing
array := [...]int{1, 2, 3, 4, 5}
// Declare an integer array of 5 elements, but we just define 2 first element, so rest of array will store zero value of array's type.
array := [5]int{1:1, 4:4}
In this case, array is [0, 1, 0, 0, 4]
Enter fullscreen mode Exit fullscreen mode

Working with array

  • To access individual element by index, we use []. Ex: array[2]
  • We can have an array of pointers, we use * to access value that pointer points to.
// Declare an array of pointers
array := [5]*int{0: new(int), 1: new(int)} 
// Assign value to pointer at index 1
*array[1] = 10
// Because we don't initialize value for rest of array, it will be zero value of pointer - nil.
// fmt.Println(array) -> [<nil> 0x2a73258360d0 <nil> <nil> <nil> 0x2a73258360d8]
Enter fullscreen mode Exit fullscreen mode
  • Array is a value in Go, so we can assign it to other arrays, but that arrays need to same element's type and same length, if not, this raise error compiler.
  • Similar, copy an pointer array will copy all pointer from source array, not copy value of pointers point to.
  • We can create nested array, array contains array, this allow us to can create multidimensional array. #### Passing array between functions
  • When we passing array between functions, it always passed by value. This mean we will copy all value of array, regardless entirely type of elements and size of array, for large arrays, it not effective for both memory and performance.
  • In case large arrays, we should pass pointer of array between function, because we just pass address of array, not entire array, but when do it, we also aware that when we modify value of element of array, outer function will impact. ## Slices #### Overral
  • Slices are built around the concept of dynamic arrays, that can be modify size quickly. Underlying in memory of slice is contiguous block, so it offer you all benefit of indexing, iteration and garbage collection.
  • Slices have three metadata field: pointer address (pointer to underlying memory) , length and capacity. #### Declare
  • We can declare a slice by make function, specific length and capacity of slice (if capacity not specific, it could be same with length)
slice := make([]string, 5) 
slice_specific_cap := make([]string, 5, 10)
Enter fullscreen mode Exit fullscreen mode
  • An idiomatic way of creating a slice is to use a slice literal. It quite similar with array literal, except we don't put length of slice in [], the length and capacity are based on length of values that we initializing
 slice := []string{'a', 'b', 'c'}
Enter fullscreen mode Exit fullscreen mode
  • We can specific index of initial value, similar array:
slice := []string{99: 'a'}
Enter fullscreen mode Exit fullscreen mode
  • Note: when we put length in [], we create an array, if not, we create a slice
  • Create nil slice: var slice []int
  • Create zero slice: slice := make([]int, 0) or []int{} #### Working with slice
  • I think it similar in list in Python, about index, changing share memory,.. but I notice that create slice from source slice have something to be aware.
  • Create new slice for exists slice. Ex:
 sliceSrc := []int{0, 1, 2, 3, 4}
 sliceNew := slice_src[1:3] // [1, 2]
Enter fullscreen mode Exit fullscreen mode

After that, length of sliceNew is 2, but capacity of it is 4.
We have notice: for slice[i:j] with an underlying array of capacity k
length: j - i (3 - 1 = 2)
capacity: k - i (5 - 1 = 4)
Because this behavior is just create new slice from an exists underlying array, slice_new have pointer points to element index 1 in sliceSrc. So, if we modify index 1 in sliceSrc, the value of pointer 0 of sliceNew pointing will be change, similar potential problem of copy array in Python

  • Growth of slice: one of the advantage of using a slice over an array is that we can growth the capacity of our slice as needed. We do this by using built-in function append.
slice := []int{1, 2, 3}
newSlice := append(slice, 4) ! [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Notice that append a slice can change value of exists underlying array, because it is contiguous block in memory.
When base capacity doesn't enough for new value, Go automatically create new underlying array with exist value, and growth the capacity of new underlying array, based on current number( if number under 1000, it will be 2, and if number greater 1000, it will be 1.25 and this is just current rule)

  • Because of potential issue when append value to new slice, we can protect it by third index when assign new slice. newSlice := oldSlice[2:3:4] newLength: 3 - 2 = 1 newCapacity: 3 - 2 = 1 In this case, newSlice has newLength and newCapacity is 1, because of this, when we call append for newSlice to add new value, Go need to create new underlying array, and we will prevent any potential bug from sharing underlying array. Notice that we can't create new capacity large than capacity of exist slice, it will raise runtime error.
  • We can append multiple value to a slice with one call.
append(slice, 2, 3, 4) or append(slice, sliceOther...)
Enter fullscreen mode Exit fullscreen mode
  • Iterate slice: for index, value := range slices. Range will return two value, index and value of this index. Value return is copy of the value, not referrence.
  • We also iterate a slice by for index, similar other language:
for index:=0; index < len(slice); index++ 
Enter fullscreen mode Exit fullscreen mode

Passing slice between functions

  • When we pass slice between functions, it requires nothing more than passing the slice by value because we just copy the slice pass to function, not contains underlying array, and because of this, need to be careful when change value of underlying array.

Maps

Overall

  • A maps is a data structure similar dict in Python, store collection of data by key/value pairs. A maps is unorder, although we store in same order, the order of pair when we iterate always different. Maps use hash tables internal. A hash table of key helps determine where key/value pairs stored and located efficiently. Because of this, key of maps require hashable, that mean it always return same value when hash every time (int, string, .. not slices, maps and function) #### Declare
  • We can use map function or literal maps
 // Declare use map function
maps := make(map[string]int) # map with string key and int value
// Declare use maps literal
dict := map[string]int{"a": 1, "b": 2}
Enter fullscreen mode Exit fullscreen mode

Working with maps

  • A nil map can't use to store key/value pairs, but zero map can use.
  • Get value of key in maps can return two value: value and flag exists key. When key exists, it return value and True, but when key not exists, value return zero values of map value and False for flag exists key.
value, exists := maps["key"]
Enter fullscreen mode Exit fullscreen mode
  • We can assign a key/value pair to maps by : maps[key] = value
  • We can remove a key/value pair in maps by: delete(maps, key), and if key is not exists, it will not raise any error.
  • Iterate maps return key and value for each pair
 for key, value := range maps
Enter fullscreen mode Exit fullscreen mode

Passing maps between functions

  • Maps are pass by value. However, the copied hash value still refers to underlying hash table, so modify maps in function also make caller maps.

So this is some main point I learn about arrays, slices and maps in Go. I feel they quite similar concept in Python, but have some new concepts about capacity of underlying arrays.
Thank you for take time to read my blog. Good luck for your journey!

Top comments (0)