Skip to content

Latest commit

 

History

History
247 lines (177 loc) · 6.65 KB

maps.md

File metadata and controls

247 lines (177 loc) · 6.65 KB

Maps

Initialization

In Python, a collection of (key, value) pairs with unique keys is known as a dictionary. It is a built-in type called dict and is declared either of the following ways (although the first is far more prominent):

aliases = {}
aliases = dict()

This same structure exists within Go as a map. But since Go is a typed language, maps have types for both keys and values:

var aliases map[string]string

However, attempting to insert into this variable will cause a runtime panic:

aliases["Batman"] = "Bruce Wayne"
panic: assignment to entry in nil map

Before use, it must be "made" with the built-in function make:

aliases = make(map[string]string)

This declare and make syntax is clunky. Map initialization is better done with short variable declarations - if possible - by using either of the following:

aliases := map[string]string{}
aliases := make(map[string]string)

Since type is inferred in the short form, it is impossible to use an un-made map. Any syntax mistakes made during declaration become compile-time errors:

aliases := map[string]string
syntax error: unexpected semicolon or newline, expecting {

Using the first short format also allows the declaration of initial values, much as like python:

aliases := map[string]string{
    "Superman": "Clark Kent",
}

Declaring duplicate keys during initialization is a compile-time error:

aliases := map[string]string{
    "Green Lantern": "John Stewart",
    "Green Lantern": "Hal Jordan",
}
duplicate key "Green Lantern" in map literal

In contrast, Python allows duplicate keys during initialization, and will use the last given duplicate as the key's value:

aliases = {
    "Green Lantern": "John Stewart",
    "Green Lantern": "Hal Jordan",
}
{'Green Lantern': 'Hal Jordan'}

Although Python dictionaries are untyped - not all types are valid keys. Only hashable Python types can be used as keys, which excludes list, set, and other dictionaries. Using these types as keys will generate a runtime TypeError exception:

aliases[dict()] = 'value'
TypeError: unhashable type: 'dict'

There are also illegal key types in Go, but using them will generate a compile-time error:

var nested map[map[string]string]string
invalid map key type map[string]string

In Python, frozenset, tuple, and custom classes can be used to provided multi-dimensional keys. In Go, this can also done with using custom struct types or using arrays - which are hashable.

matrix := map[[2]int]string{
    [2]int{1, 2}: "Battleship",
}

As opposed to slices - which are not.

matrix := map[[]int]string{
    []int{1, 2}: "Battleship",
}
invalid map key type []int

TODO

  • Python can build dictionaries from iterables - Go equivalent?
  • Caveats of interface{} as a key in Go - especially vis-à-vis type equality

Usage

Both Python and Go maps are mutable in the sense that keys can be overwritten post-initialization.

For maps, the built-in length function len in Go is equivalent to that in Python:

len(aliases)

To delete, Python uses the del keyword, whereas Go uses another built-in function called delete:

delete(aliases, "Green Lantern")

Existence is checked in Python using the in operator:

print("Green Arrow" in aliases) # False

There is no existence function, operator, or method in Go. Checking existence in Go is further complicated by non-existent keys returning the zero values of their value types:

ages := map[string]int{}
fmt.Println(ages["Bill"]) // 0

Existence must be checked by using the two variable assignment syntax:

ages := map[string]int{}
age, exists := ages["Bill"]
fmt.Println(age, exists) // 0, false

If the first variable is irrelevant, it can be ignored using the blank identifier _:

ages := map[string]int{}
_, exists := ages["Bill"]
fmt.Println(exists) // false

The benefit of Go maps returning zero values for non-existent keys is that many values can be modified without initialization, for example:

villians := map[string][]string{}
villians["Batman"] = append(villians["Batman"], "The Joker", "Ra's al Ghul")

The equivalent behavior in Python would require the usage of the collections module's defaultdict:

from collections import defaultdict
villians = defaultdict(list)
villians["Batman"].extend(["The Joker", "Ra's al Ghul"])

TODO

Iteration

Iterating over a dictionary in Python returns the key as the single iterable variable.

for key in aliases:
    print(key)

Using range in Go will also return the key:

for key := range aliases {
    fmt.Println(key)
}

But iteration over key - value pairs is as simple as providing another destination variable:

for key, value := range aliases {
    fmt.Println(key, value)
}

Whereas Python requires usage of the items method:

for key, value in aliases.items():
    print(key, value)

In the CPython implementation of Python, dictionary iteration can vary, but will remain consistent as long as the dictionary is not modified.link

Go does not guarantee iteration order, even between back-to-back iterations.link

Methods

Python provides a number of methods for dictionaries, including the keys and values methods that return a dictionary's keys and values, respectively, as lists.

Go maps have no built-in methods, though the keys and values methods can be replicated by attaching them to custom types:

type Counter map[string]int

func (c Counter) Keys() (keys []string) {
    for key := range c {
        keys = append(keys, key)
    }
    return
}

However, since Go lacks generics, these methods must be declared for each custom type.

From Golang for Pythonistas

Happy hacking!

aodin, 2015