What a range loop over a map does
A for loop with range visits every entry of a map once. On each turn it hands you the key first and the value second:
stock := map[string]int{"apples": 5, "pears": 2}
for fruit, amount := range stock {
fmt.Println(fruit, amount)
}
This prints both entries, apples 5 and pears 2, but not in a promised order.
Keys only, or values only
Write one variable and range gives only the keys. You can still read each value by indexing the map:
for fruit := range stock {
fmt.Println(fruit, stock[fruit])
}
If you need only the values, put the blank identifier _ where the key would go: for _, amount := range stock. Go refuses to compile a variable that is declared and never used, so _ is how you say "I don't need this one".
The order is random on purpose
Go does not visit a map in insertion order or in sorted order. It picks the starting point at random, so two runs of the same program can print the entries in different orders. Don't write code that depends on which key comes first.
Some answers do not depend on the order. A total, a count, or a "does any entry match" check come out the same however the entries are visited. When you do need a fixed order, collect the keys into a slice, sort it with the sort package, and loop over the slice. In a file of your own, that looks like this (it needs import "sort"):
keys := []string{}
for k := range stock {
keys = append(keys, k)
}
sort.Strings(keys)
fmt.Println(keys)
This prints [apples pears] every time.
The lesson's task
The map scores holds each player's points. The task is to add every player's points to total and to count the players with 10 points or more in count. Both answers are order-independent, so the random order is harmless. The names are not needed, so the key goes into _.
- Write
for _, points := range scores {and close it with}. - Inside, add
pointstototal. - Still inside, if
pointsis at least 10, add 1 tocount.
scores := map[string]int{"Ana": 12, "Ben": 7, "Cleo": 15, "Dan": 9}
total := 0
count := 0
for _, points := range scores {
total += points
if points >= 10 {
count += 1
}
}
fmt.Println(total)
fmt.Println(count)
The output is:
43
2
The points are 12, 7, 15 and 9, which add up to 43. Ana (12) and Cleo (15) have 10 or more, so the count is 2.
Because a map is only ever visited as a whole, a loop over one is a natural fit for summaries: how many entries pass a test, what they add up to, or which is the largest. Each of those results is the same however the entries are ordered, which is why the lesson asks for a total and a count and nothing that prints names.
Common mistakes
- Leaving the key as a named variable.
for name, points := range scoresdoes not compile whennameis never used. Use_. - Expecting a fixed order. Printing inside the loop can give a different order on each run. Sort the keys first if the order matters.
- Putting the
ifoutside the loop. Then it sees only the lastpointsvalue, not each one in turn.
Key takeaways
for key, value := range mvisits every entry once; one variable gives only the keys.- Use
_for the part you don't need. - The visiting order is random, so use loops over maps for totals, counts and checks, or sort the keys first.
