Time for maps! 🗺️ and just like maps we use to point to specific locations in
the world, a map
in Go is no different! We use a key
to point to a value
.
In other languages they are called Dictionaries, Hash Tables, or Associative
Arrays.
Wikipedia
provides
us with a showing of just how timeless ⏳ this data structure truly is.
Setup
Let’s make our directory maps
and the files we want inside of
that directory example_test.go
maps.go
mkdir maps
touch maps/example_test.go maps/maps.go
Now let’s open up maps.go
and for the very first line we’ll add
package maps
Next for example_test.go
for the very first line we’ll add
package maps_test
We can import basics/maps
into cmd/main.go
and run functions
from there with go run cmd/main.go
and also to run our example_test.go
👍
we use go test maps/example_test.go
in the commandline.
Basic Operations
When using a map
there really is only three 3️⃣ things to worry about.
- Adding key-value pairs
- Removing key-value pairs
- Getting key-value pairs.
The same syntax we saw in the previous lesson on slices apply to maps. So we
can set keys myMap[0] = 4
like we do with a slice index mySlice[0] = 4
. So
it has the same functionality that a slice does, then why bother ❓ Well, we
can do more than just int
! For example: myMap["myString"]
,
myMap[myStruct]
, myMap[false]
are all valid!
OK we’ve just learned it has more functionality than slices, 🤔 does that make slices obsolete? Well no, the two data structures serve different purposes. A map does not have a guaranteed order for it’s values.
When do we use a map
in place of a slice
? Well like in real life, when we
have some map 🗺️ we have a specific location we’re pointing to. We know
that we want to go a certain place given key
coordinates; with a slice
we
can put all of our gophers
in a row.
Think of it like a hammer 🔨 and a screwdriver🪛. We could probably screw in a screw with the back of a hammer and smash a nail with the end of a screwdriver, but it’d be much easier using the right tool for the right job.
Therefore when we have values that we want to be able to point to specifically,
and want to be able to add and remove things quickly without worrying about
order, we’ll use a map
. When we have values that we want in an order, we’ll
use a slice
.
Coding Time!
maps.go
// Basic shows how to make a new map and how to add and remove keys and
// values to it.
func Basic() {
myMap := make(map[string]int, 4) // We can leave out the size too!
myMap["key1"] = 100
myMap["key2"] = 20
myMap["key3"] = -3
myMap["key4"] = 0xc0ffee
fmt.Println("print myMap:", myMap)
delete(myMap, "key1")
delete(myMap, "key2")
delete(myMap, "key3")
delete(myMap, "key4")
fmt.Println("empty after deleting myMap keys:", myMap)
}
example_test.go
func ExampleBasic() {
maps.Basic()
// Output:
// print myMap: map[key1:100 key2:20 key3:-3 key4:12648430]
// empty after deleting myMap keys: map[]
}
Check for values
Maps 🗺️ need to be updated from time to time because location can change, but you wouldn’t update a location if it hadn’t been changed, right? And also what if Google maps told you to take a right on a road that has a no entry 🚫 sign! So we need a way to check ✅ if a value exists for a certain key then we can do something with it.
Coding Time!
maps.go
// ValueExists shows how to check if a value exists in a given map or not.
func ValueExists() {
inlineMap := map[string]string{"key1": "VALUE1", "key2": "VALUE2"}
// Will return zero values if the value is not present.
val1, present := inlineMap["key1"]
fmt.Printf("value: %v is present? %t\n", val1, present)
none, found := inlineMap["non-existent"]
fmt.Printf("value: %v is present? %t\n", none, found)
if val, exists := inlineMap["key2"]; exists {
fmt.Println("value:", val, "is there do something with it.")
}
if val, ok := inlineMap["non-existent"]; ok {
fmt.Println("This statement will never be reached!", val)
}
}
example_test.go
func ExampleValueExists() {
maps.ValueExists()
// Output:
// value: VALUE1 is present? true
// value: is present? false
// value: VALUE2 is there do something with it.
}
Set
We just saw that a slice
is used when we want a collection of a certain
type
that has an order to it. We now know we want to use a map
when we
don’t care about the order and just want to be able to grab a value from a
given key (key-value pair). Well, what if we don’t care about the order and
we don’t care about any value?
Enter the
Set
. If you
come from another programming language you are probably already aware what it
is. A set
has attributes of both a slice
and a map
. It only has values
inside of it (like a slice), but it has no order (like a map).
It is important to note there is no actual set
in Go, but that’s because we
don’t need one. A set
is just a map
with no values. 🪄 Ta da!
There is something important to note, however❗ In Go, the empty struct
struct{}
is much like the nil
value because it signals nothing is there,
and what makes it special is, it is a type
. So when reading Go code, if you
find a struct{}
somewhere, just know it’s a placeholder. It only signals to
the reader, us, that a type
is required, but we don’t use that type
for
anything, just the underlying structure. More info on why
struct{}
is
useful
Coding Time!
maps.go
// AsSet demonstrates how to create a `set` data structure -- an unordered
// collection of some type with very quick lookup and insertion -- the idomatic
// Go way.
//
// The time to use a `set` is when you don't care about the order of your
// values and you want to be able to add, delete, or get values instantly!
func AsSet() {
type important string
// struct{} takes up no space. When we see it we can think of a typed `nil`
mySet := make(map[important]struct{})
// We don't have to explicitly type cast these as "[ANYTHING HERE]" is an
// untyped string and will type itself to what it needs to at runtime.
mySet["fast lookup"] = struct{}{}
mySet["unordered"] = struct{}{}
mySet["fast insert"] = struct{}{}
mySet["collection"] = struct{}{}
// Simulate getting a string from some other function. We want to check if
// our `set` has that key, so we type cast it `important(...)` and do
// something with the information that our `set` has that key.
gotFromOtherFunc := "fast insert"
if _, found := mySet[important(gotFromOtherFunc)]; found {
fmt.Println("We found the key we were looking for!")
delete(mySet, important(gotFromOtherFunc))
} else {
fmt.Println("We didn't find that key, so let's do something else!")
mySet[important(gotFromOtherFunc)] = struct{}{}
}
}
example_test.go
func ExampleAsSet() {
maps.AsSet()
// Output:
// We found the key we were looking for!
}