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

Sort and summarize values 🔥

Once you’ve collected your puzzle data, you usually need to make sense of it. Which values are largest or smallest? What’s the average? Which readings cross a threshold? Sorting, reducing, filtering, and matching values back to their source can turn a pile of observations into answers.

In this chapter, you’ll return to the station readings and look at them from several angles. You’ll sort them to find the highest values, calculate an average, filter for extremes, and pair readings with their original labels. Then you’ll skip the sort entirely and find the minimum and maximum in a single pass, because sometimes the best way to summarize data is to avoid doing work you don’t need.

Mojo with tallied data. Mojo with tallied data.

The station readings

Create sort_and_summarize_values.mojo. Read station_reports.txt and pull out each reading’s temperature, alongside a label so you can trace a value back to the entry it came from:

    var log = Path("src/downloads/station_reports.txt")
    var lines = log.read_text().splitlines()

    var labels = List[String]()
    var readings: List[Float64] = []

    for line in lines:
        # ['Station 3', 'Day 0', '06:00', '-22.3C', 'Clear']
        var fields = line.split(", ")
        labels.append(String(t"{fields[0]}, {fields[1]}, {fields[2]}"))
        readings.append(Float64(fields[3].removesuffix("C")))

    var count = len(readings)
    print(t"{count} readings")  # 15 readings

Sort to rank

Sorting orders the list, so you can pick the coldest and warmest readings from the two ends.

Import sort at the top of your file:

from std.builtin.sort import sort

Sort a copy so the original order stays intact:

    var ordered = readings.copy()
    sort(ordered)
    print(t"sorted: {ordered}")
    # [-41.7, -32.0, -31.5, -30.1, -29.0, -28.4, -24.0, -23.1, -22.3, -22.3,
    #  -21.7, -21.0, -20.5, -18.5, -18.2]
    print(t"coldest: {ordered[0]}, warmest: {ordered[count - 1]}")

Takeaways

  • sort() is a free function, not a method. It sorts in place and ascending, so it returns nothing and rewrites the list you pass.
  • Sort a .copy() when you need the original ordering. Here readings stays aligned with labels.

Top of the list

Once a list is sorted, the largest values are at the end. Slice off the last three to retrieve them in ascending order:

    print(t"top 3 warmest: {ordered[(count - 3):]}")  # [-20.5, -18.5, -18.2]

Takeaways

  • This provides the “top three” that many puzzles ask for: sort once, then take from the end.

Reduce your data

You’ve been asked to find the average temperature across every reading.

Iterate a sum and divide by count.

    var total: Float64 = 0.0
    for r in readings:
        total += r

    print(t"average: {round(total / Float64(count), 1)}")  # -25.6

Takeaways

  • round(value, digits) tidies a float for display. Floating-point totals carry representation noise. round() helps you control that.
  • List has no built-in reductions like sum(), min(), or max(). Accumulate across a loop instead. The min() and max() in std.math compare two values, not a whole list.

Filter it down

Use a comprehension filtered for the most extreme readings to build a new list:

    var extreme = [r for r in readings if r < -30.0]
    print(t"extreme readings: {extreme}")  # [-30.1, -31.5, -32.0, -41.7]

Takeaways

  • [expr for x in it if cond] keeps only the elements where cond is true.
  • The filter runs as the list builds, so you never construct the rejects.

Pair the readings with their labels

The labels list has important information you need to tell which station and day a reading belongs to.

zip() combines two lists, allowing you to walk through paired values in step. Use this to align readings with their labels:

    for label, r in zip(labels, readings):
        print(t"{label}: {r}")

Takeaways

  • zip() yields a tuple per step, unpacked here into label and r.

Match the readings to the extremes

Sorting orders every reading when you only need two of them. A single pass finds both extremes without that work:

    var max_value = Float64.MIN  # -inf, so any reading beats it
    var min_value = Float64.MAX  # +inf, so any reading undercuts it

    for r in readings:  # one pass finds both extremes
        if r > max_value:
            max_value = r
        if r < min_value:
            min_value = r

    # max_value: -18.2 (warmest), min_value: -41.7 (coldest)

Knowing the values helps you find their original indices in readings:

    try:
        var min_index = readings.index(min_value)
        print(t"Coldest reading: {labels[min_index]} at {min_value}°C")
        var max_index = readings.index(max_value)
        print(t"Warmest reading: {labels[max_index]} at {max_value}°C")
    except e:
        print(e)

Wrapping your calls to index() in a try/except statement ensures that any errors will be caught and reported.

If you’d like to see that error in action, tweak either value. For example .index(min_value + 1.0).

Final code

Your complete sort_and_summarize_values.mojo:

from std.pathlib import Path

from std.builtin.sort import sort



def main() raises:
    var log = Path("src/downloads/station_reports.txt")
    var lines = log.read_text().splitlines()

    var labels = List[String]()
    var readings: List[Float64] = []

    for line in lines:
        # ['Station 3', 'Day 0', '06:00', '-22.3C', 'Clear']
        var fields = line.split(", ")
        labels.append(String(t"{fields[0]}, {fields[1]}, {fields[2]}"))
        readings.append(Float64(fields[3].removesuffix("C")))

    var count = len(readings)
    print(t"{count} readings")  # 15 readings

    var ordered = readings.copy()
    sort(ordered)
    print(t"sorted: {ordered}")
    # [-41.7, -32.0, -31.5, -30.1, -29.0, -28.4, -24.0, -23.1, -22.3, -22.3,
    #  -21.7, -21.0, -20.5, -18.5, -18.2]
    print(t"coldest: {ordered[0]}, warmest: {ordered[count - 1]}")

    print(t"top 3 warmest: {ordered[(count - 3):]}")  # [-20.5, -18.5, -18.2]

    var total: Float64 = 0.0
    for r in readings:
        total += r

    print(t"average: {round(total / Float64(count), 1)}")  # -25.6

    var extreme = [r for r in readings if r < -30.0]
    print(t"extreme readings: {extreme}")  # [-30.1, -31.5, -32.0, -41.7]

    for label, r in zip(labels, readings):
        print(t"{label}: {r}")

    var max_value = Float64.MIN  # -inf, so any reading beats it
    var min_value = Float64.MAX  # +inf, so any reading undercuts it

    for r in readings:  # one pass finds both extremes
        if r > max_value:
            max_value = r
        if r < min_value:
            min_value = r

    # max_value: -18.2 (warmest), min_value: -41.7 (coldest)

    try:
        var min_index = readings.index(min_value)
        print(t"Coldest reading: {labels[min_index]} at {min_value}°C")
        var max_index = readings.index(max_value)
        print(t"Warmest reading: {labels[max_index]} at {max_value}°C")
    except e:
        print(e)

Topics covered

Sorting, filtered comprehensions, zip(), and locating values with index().