martincartledge.github.io

This is the fourth entry of my weekly series Learning Go. Last week I covered a few common types in Go: Boolean, Numeric, String, Array, and Slice. This week I will cover a few more pieces of Slice, and will talk about the Map type as well.

Slicing items in a Slice

Pulling out values from one Slice into another is made easy in Go; this is called slicing. This is done by specifying a half-open range with two indices, separated by a colon [:]. I will demonstrate below:

package main

import (
	"fmt"
)

func main() {
	x := []int{4, 5, 6, 7, 8}
	y := x[1:3]
	fmt.Println(y)
	// [5 6]
}

A few things to note about slicing:

Let me give you another example

package main

import (
	"fmt"
)

func main() {
	x := []int{4, 5, 6, 7, 8}
	fmt.Println(x[:4])
	// [4 5 6 7]
}

Above we are slicing a Slice; however, in this example we omit the first indices.

As you can see, when we omit the first indices it will default to 0, and if we were to omit the second indices it would default to the length of the Slice.

The result is taking all values in the Slice until the fourth indices.

Variadic functions

Adding more values into a Slice in Go is made easy by using the built-in variadic function, append. Quick note: a variadic function is a function that can take zero, or any number of trailing arguments. Let’s see an example of a variadic function first:

package main

import (
	"fmt"
)

func count(x ...int) {
	for _, v := range x {
		fmt.Println("The current value is: ", v)
	}
}

func main() {
	n := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	count(n...)
}

Appending values in a Slice

As mentioned above, using the append function is a quick and easy way to add values to a Slice in Go. The append function takes the original Slice as the first argument, and the values as the second argument. The second argument can of course be an arbitrary amount since it is a variadic function. The append function returns a Slice of the same type.

package main

import (
	"fmt"
)

func main() {
	n := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	n = append(n, 11, 12, 13, 14, 15)
	fmt.Println(n)
	// [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
}

Let’s see what happens if you try to use two different types when calling an append function.

package main

import (
	"fmt"
)

func main() {
	n := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	n = append(n, "not", "an", "int")
	fmt.Println(n)
	// cannot use "not" (type untyped string) as type int in append
}

cannot use "not" (type untyped string) as type int in append

Go makes it easy for you to write code that will not produce inconsistent types.

Removing values in a Slice

Go makes removing values from a Slice very intuitive. We will also be using slicing when we want to remove items from a Slice.

package main

import (
	"fmt"
)

func main() {
	n := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	n = append(n, 11, 12, 13, 14, 15)
	fmt.Println(n)
	// [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]

	n = append(n[:3], n[4:]...)
	fmt.Println(n)
	// [1 2 3 5 6 7 8 9 10 11 12 13 14 15]
}

Map

unordered group of elements of the same type, indexed by a set of unique keys of another type - called the “key type”

A few things to note about Map:

Let’s see a few common uses of Map in action:

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}
	fmt.Println(m)
	// map[yoda:900, obi wan: 34]
}

A cool thing about Map is if you try to access a value by key and it does not exist, it will still return a zero value. This can be very useful in preventing an error being thrown for Map lookups. Speaking of, looking up a value in a Map via the key is super straight forward:

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}

	fmt.Println(m["yoda"])
	// 900
}

As mentioned earlier, lookups in Maps are very quick and efficient. This is because when Go is given the explicit key to a value there is not any outstanding time or space complexity.

Above, we grab the value of yoda simply by using bracket notation and passing the key "yoda".

What if a specified key is not found? I will show you a quick and simple way to handle this case, called the Comma Ok Idiom.

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}

	if v, ok := m["yoda"]; ok {
		fmt.Println("found yoda's age, you have", v)
		// found yoda's age, you have 900
	}
}

The Comma Ok Idiom allows you to write defensive code in just a few lines.

Adding an element to a Map

Go makes adding elements to a Map easy peasy. Let me show you how it is done:

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}

	// adds element to a map
	m["darth vader"] = 24

	fmt.Println(m)

	// map[darth vader:24 obi wan:34 yoda:900]
}

To add a key value pair to a Map, you simply follow this syntax m["key"] = value

As seen above, we add the key "darth vader" and the value 24 to our Map m

Looping in a Map

Looping in Maps is very common, just as they are in Arrays or Slices. In Maps, they are just as easy as well.

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}

	// loop over map and print key value pairs
	for k, v := range m {
		fmt.Println(k, v)
		// yoda 900
		// obi wan 34
		// darth vader 24
	}
}

As you can see, when looping over a Map, the syntax to do so is very similar to looping over an Array or Slice. The main difference here is the use of k (key) instead of i (index). This is due to the structural differences of Map.

Removing an element from a Map

Sometimes something just has to go. Luckily this action is accomplished painlessly.

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}

	delete(m, "yoda")
	fmt.Println(m)
	// map[obi wan: 34]
}

It isn’t a bad idea to use the Comma Ok Idiom when wanting to remove items from a Map conditionally as well:

package main

import (
	"fmt"
)

func main() {
	m := map[string]int{
		"yoda":    900,
		"obi wan": 34,
	}

	if _, ok := m["yoda"]; ok {
		delete(m, "yoda")
	}
	fmt.Println(m)
	// map[obi wan:34]
}

Much like in the last example of using the Comma Ok Idiom, we create an if statement that has two return values, the value of the key and a bool value once the expression is evaluated.

In Summary

The more I explore the fundamentals of these common types, the more excited I get. I love that Go makes common actions of these types so easy (adding, removing, shifting, etc). Next week I will cover two more data types I have used that are of equal importance: Structs and Interfaces. See you then!