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 ClosedRange
s. A single section range in the input looks like the following: 2-4
so we’ll split on -
, convert the two parts to Int
s 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]...parts[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 ClosedRange
s 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:
elfZonePairs
.filter { $0.leftZone.contains($0.rightZone) || $0.rightZone.contains($0.leftZone) }
.count
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:
elfZonePairs
.filter { $0.leftZone.overlaps($0.rightZone) }
.count
And that’s it for today!