This post contains inline annotations/footnotes to help add context, helpful tips or expand upon a tangent in the text. Expand them by clicking or tapping on them Annotations might have their own annotationsSuch as this one! inside of them too.

I felt that today’s puzzle was rather easy, but I acknowledge that a big part of that is because the swift language and standard library had all of the tools necessary built in to solve the problem with minimal fanfare. I also started solving the second part without knowing it before I finished reading the first part’s challenge, lol.

Part One

In how many assignment pairs does one range fully contain the other?

Our input for today maps two elves per line, where each elf is assigned a contiguous range of sections to clean up. We’ve got to find how many of these elves are assigned to clean up sections that are completely assigned to their pair already. An elf assigned to 2-4 in the input is actually assigned to sections 2, 3, and 4 and this is starting to look awfully like the concept of a range of numbers. A lot of us have probably seen the following code in some form before:

for let i in 0..<6 { }
// OR
for let i in 0...5 { }

The ..< and ... operators are actually creating a range object in the background: Range for ..< which is a half-open range from the lower bound up to but not including the upper-bound and ... for ClosedRange which is from the lower bound up to and including the upper bound. For the sake of convenience today, we’ll be using ClosedRanges. A single section range in the input looks like the following: 2-4 so we’ll split on -, convert the two parts to Ints and then map them to a ClosedRange with ...:

func sectionToRange(_ sectionString: Substring) -> ClosedRange<Int> {
    let parts = sectionString
        .split(separator: "-", maxSplits: 1)
        .map { Int(String($0))! }

    return parts[0][1]

To make things a little easier to understand, as per usual we’ll combine this into a container struct per line. Today’s will hold the two elves zone’s ClosedRanges and we’ll use our helper from above to build the two ranges:

struct ZonePairs {
    let leftZone: ClosedRange<Int>
    let rightZone: ClosedRange<Int>

func parseLine(_ line: Substring) -> ZonePairs {
    let parts = line.split(separator: ",", maxSplits: 1)

    return .init(
        leftZone: sectionToRange(parts[0]),
        rightZone: sectionToRange(parts[1])

Here’s the neat bit, though: Range turns out to be incredibly helpful for us because it has contains which pretty much solve this first part of the challenge. Now that we’ve got our input mapped to an array of ZonePairs we can filter the array down to pairs where either the left or the right zone fully contain the right or left zone respectively and then simply count the number of zone pairs left:

    .filter { $0.leftZone.contains($0.rightZone) || $0.rightZone.contains($0.leftZone) }

Part Two

In how many assignment pairs do the ranges overlap?

This part is actually even easier than the first part. As we’ve just seen, Range has a lot of provisions already, so we can go looking for something that’ll tell us if one Range overlaps another Range and sure enough we’ll come across overlaps. Now, instead of filtering down to the set of zone pairs that contains the other pair, we’re just filtering them down to the pairs that overlap:

    .filter { $0.leftZone.overlaps($0.rightZone) }

And that’s it for today!