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]
}
- first we assign the variable
x
to a Slice of the type int with the values4 5 6 7 8
using a composite literal - next, we assign the variable
y
to the value ofx
from the indices1
up until but not including indices3
- the result gives us a Slice with the values
5
and6
A few things to note about slicing:
- the first and last indices of the slice expression are optional
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...)
}
- we create a function called
count
count
has a single parameterx
- what makes the
count
function variadic is the...
syntax after thex
parameter - this is how we tell the Go compiler to allow any number of arguments for
x
- in the
main
function you will see that we use the...
syntax when we call thecount
function - this is how we are able to pass an arbitrary amount of arguments into
count
- by using the
...
syntax we are letting the Go compiler know that there could be 10 items in this slice, or 100 items
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]
}
- inside of the
main
function, we declare the variablex
and set the value to a Slice of typeint
that contains1
through10
- on the next line, we re-assign the variable
n
to the return value of theappend
function - inside the
append
function we passn
as the first argument - note this is a Slice of typeint
- the second argument is the values
11
through15
- remember this is a variadic function meaning it can have any number of trailing arguments
- second argument values must be of the same type that the first argument Slice contains
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
}
- because the arguments in
append
were not the typeint
the Go compiler throws an error and displays this message:
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]
}
- inside of the
main
function, we declare the variablen
and set the value to a Slice of typeint
with the values1
through10
- next, we
append
the values11
,12
,13
,14
, and15
to the Slicen
- let’s say that we want to remove the value
4
fromn
- we do this by using slicing syntax
[:]
- we passing in the values of the Slice
n
up until the third indicesn[:3]
- our second argument starts with the value at the fourth indices of
n
n[4:]
- since there is no value on the right-hand side of the
:
we take all of the rest of the values inn
- you’ll notice we see the
...
syntax again, this is to ensure we can take an arbitrary amount of arguments in case the size ofn
grows
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:
- structured as a
key
value
store - very fast and efficient lookup
- unordered
- commas are needed after each entry in a 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]
}
- inside of the
main
function, we declare the variablem
that is assigned to amap
- the
map
’skey
will be of typestring
- the
map
’svalue
will be of typeint
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.
- first we write an
if
statement that has two return values,v
(value) andok
(condition is evaluated totrue
) - since our Map contains the
key
"yoda"
we step into this block and print this statement, along with thevalue
of the"yoda"
key
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]
}
- the
delete
function is built into the Map type, and as you can see it is very easy to use delete
takes two arguments: the Map that you want to remove something from, and thekey
of the entry that you wish to remove
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.
- here, the value of
ok
will evaluate totrue
- we are throwing away the first return (
value
) because we don’t need it in this exercise - because
ok
evaluated totrue
, we step into thisif
block and call thedelete
function - we pass the Map, in this case
m
as the first argument, and thekey
"yoda"
as the second argument - we print out the value of
m
and see that thekey
value
pair foryoda
and900
has been removed
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!