Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

Mojo tallying data. Mojo tallying data.

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 a Dict[Int, Int].
  • counts.get(id, 0) returns the stored count, defaulting to 0 for absent keys. That default is what makes the counter work on a station’s first reading.
  • counts.items() walks the pairs. Each entry carries entry.key and entry.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, add is 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 set is 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 in expected that isn’t in reported.
  • 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 Tuple groups 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.key unpacks 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.