Count and look up values 🔥
Advent of Code puzzles often give you a pile of values and ask questions about the pile. How many times did each value appear? Have you seen this one before? What’s missing? Which combinations occurred? Choosing the right collection can turn each of those questions into a straightforward operation.
In this chapter, you’ll investigate a batch of station reports using several of Mojo’s collection types. You’ll tally reports with a dictionary, use sets to find missing stations, combine station and day in tuple keys, and use a comprehension to find missing readings. Along the way, you’ll see how the shape of the question can help you choose the collection that answers it.
Your puzzle
You just got handed a puzzle with a pile of station reports. You need to identify which stations went quiet.
Here’s what you’ll need: tallies (a great match to dictionaries), membership tests (perfect for sets), and compound keys (tuples are a good choice).
The situation report
Download station_reports.txt. Every line names a station and the day it reported, arriving out of order with repeats, the way a real batch of sensor uploads would:
Create count_and_look_up_values.mojo, and read the reports:
var log = Path("src/downloads/station_reports.txt")
var lines = log.read_text().splitlines()
var reported_ids = List[Int]()
var days = List[Int]()
for line in lines:
# ['Station 3', 'Day 0', '06:00', '-18.2C', 'Clear']
var fields = line.split(", ")
reported_ids.append(Int(fields[0].split(" ")[1]))
days.append(Int(fields[1].split(" ")[1]))
# fields[2] is the time, fields[3] the temperature, fields[4] the
# conditions. This page only needs the station id and the day.
print(t"{len(reported_ids)} readings") # 15 readings
You need to turn that raw list into answers.
Tally with a dictionary
How many times did each station report? Time to use a dictionary.
A Dict maps keys to values. To count, use each station id as a key and
bump its running total. Add this to main():
var counts = Dict[Int, Int]()
for id in reported_ids:
counts[id] = counts.get(id, 0) + 1
# Key not found? Count defaults to 0.
var pairs = List[Tuple[Int, Int]]()
for entry in counts.items():
pairs.append((entry.key, entry.value))
for i in range(len(pairs)):
for j in range(i + 1, len(pairs)):
if pairs[j][0] < pairs[i][0]:
var tmp = pairs[i]
pairs[i] = pairs[j]
pairs[j] = tmp
for pair in pairs:
print(t"station {pair[0]}: {pair[1]} readings")
# station 1: 5 readings
# station 3: 4 readings
# station 5: 6 readings
Station 1 reported five times, station 3 four times, and station 5 six times. Two stations never show up at all.
Takeaways
Dict[K, V]needs a key type and a value type. The stations are numbered and so are their repetitions, so this is aDict[Int, Int].counts.get(id, 0)returns the stored count, defaulting to0for absent keys. That default is what makes the counter work on a station’s first reading.counts.items()walks the pairs. Eachentrycarriesentry.keyandentry.value.
Look up fast with a set
A Set is a collection of unique items. It contains each value once and
only once, answering “is this value in here or not?”
Build the set of stations that reported.
Sets aren’t part of Mojo’s automatic imports (the “prelude”) so add this import to the top of your file.
from std.collections import Set
Then add this to main():
var reported: Set[Int] = {}
for id in reported_ids:
reported.add(id)
for station in range(1, 6):
print(
t"Station {station} reported? "
t"{'Yes' if station in reported else 'No'}" # TStrings allow concatenation
)
# Station 1 reported? Yes
# Station 2 reported? No
# Station 3 reported? Yes
# Station 4 reported? No
# Station 5 reported? Yes
Did station 1 report? Yes. Did station 2? No.
Takeaways
- With a set,
addis idempotent. Adding a station that’s already present changes nothing, which is how the set collapses the repeats down to three distinct ids. x in setis the membership test. On a list you’d scan every element. In a dictionary, you’d have to look at the keys or check for a failed lookup. A set answers “is this here?” directly.
Who went silent
You know the roster you expected to hear from. Subtract the ones that reported and the silent stations fall out.
var expected = Set[Int](1, 2, 3, 4, 5)
var missing = expected - reported
print(t"silent stations: {missing}") # {2, 4} in either order
Like dictionaries, sets are not ordered, so you may see “{4, 2}”.
Takeaways
- Both dictionary and set literals use curly braces. Sets don’t use colons.
Dictionaries do.
{"a", "b"}is a set literal and{"a": 5, "b": 3}is a dictionary literal. -is set difference: everything inexpectedthat isn’t inreported.- Like checking a count against a total, your set checks coverage against an expected roster. Same instinct, one level up: confirm you have everything you should.
Tuples: Mojo’s anonymous types
One key isn’t always enough. To count readings per station and day, key the dictionary on a tuple.
var per_day = Dict[Tuple[Int, Int], Int]()
for i in range(len(reported_ids)):
var key = (reported_ids[i], days[i])
per_day[key] = per_day.get(key, 0) + 1
for entry in per_day.items():
var station, day = entry.key # unpack the tuple key
print(t"station {station}, day {day}: {entry.value}")
Dictionary keys must be hashable, and integer tuples fit that requirement.
Takeaways
- A
Tuplegroups a fixed set of values. If its entries are hashable, you can use them as dictionary keys.(station, day)counts each pair on its own. var station, day = entry.keyunpacks a tuple into named bindings in one step.
Comprehensions: smart data retrieval
Stations 2 and 4 never reported at all. The remaining question is narrower: among the three stations that did report, did any of them miss a day?
Build a set of the (station, day) pairs that actually showed up:
var per_day_set = Set[Tuple[Int, Int]]()
for i in range(len(reported_ids)):
per_day_set.add((reported_ids[i], days[i]))
Mojo comprehensions let you iterate and filter results in one expression:
var missing_days = [
String(t"Station {station} - Day {day}")
for station in [1, 3, 5] # the three stations that report at all
for day in range(3)
if (station, day) not in per_day_set
]
missing_days iterates over every combination of station and day
across the three reporting stations, selecting only the pairs that never
appear in per_day_set.
The result: station 1 never reported on day 1.
print(t"missing days: {missing_days}") # ['Station 1 - Day 1']
Final code
Your complete count_and_look_up_values.mojo:
from std.pathlib import Path
from std.collections import Set
def main() raises:
var log = Path("src/downloads/station_reports.txt")
var lines = log.read_text().splitlines()
var reported_ids = List[Int]()
var days = List[Int]()
for line in lines:
# ['Station 3', 'Day 0', '06:00', '-18.2C', 'Clear']
var fields = line.split(", ")
reported_ids.append(Int(fields[0].split(" ")[1]))
days.append(Int(fields[1].split(" ")[1]))
# fields[2] is the time, fields[3] the temperature, fields[4] the
# conditions. This page only needs the station id and the day.
print(t"{len(reported_ids)} readings") # 15 readings
var counts = Dict[Int, Int]()
for id in reported_ids:
counts[id] = counts.get(id, 0) + 1
# Key not found? Count defaults to 0.
var pairs = List[Tuple[Int, Int]]()
for entry in counts.items():
pairs.append((entry.key, entry.value))
for i in range(len(pairs)):
for j in range(i + 1, len(pairs)):
if pairs[j][0] < pairs[i][0]:
var tmp = pairs[i]
pairs[i] = pairs[j]
pairs[j] = tmp
for pair in pairs:
print(t"station {pair[0]}: {pair[1]} readings")
# station 1: 5 readings
# station 3: 4 readings
# station 5: 6 readings
var reported: Set[Int] = {}
for id in reported_ids:
reported.add(id)
for station in range(1, 6):
print(
t"Station {station} reported? "
t"{'Yes' if station in reported else 'No'}" # TStrings allow concatenation
)
# Station 1 reported? Yes
# Station 2 reported? No
# Station 3 reported? Yes
# Station 4 reported? No
# Station 5 reported? Yes
var expected = Set[Int](1, 2, 3, 4, 5)
var missing = expected - reported
print(t"silent stations: {missing}") # {2, 4} in either order
var per_day = Dict[Tuple[Int, Int], Int]()
for i in range(len(reported_ids)):
var key = (reported_ids[i], days[i])
per_day[key] = per_day.get(key, 0) + 1
for entry in per_day.items():
var station, day = entry.key # unpack the tuple key
print(t"station {station}, day {day}: {entry.value}")
var per_day_set = Set[Tuple[Int, Int]]()
for i in range(len(reported_ids)):
per_day_set.add((reported_ids[i], days[i]))
var missing_days = [
String(t"Station {station} - Day {day}")
for station in [1, 3, 5] # the three stations that report at all
for day in range(3)
if (station, day) not in per_day_set
]
print(t"missing days: {missing_days}") # ['Station 1 - Day 1']
Topics covered
Dictionaries as counters, get() with a default, iterating items(), sets
for uniqueness and fast membership, set differences, tuples as compound
keys, tuple unpacking, and comprehensions for generating and filtering
combinations.