--- title: "Advent of Code: Day Four" date: 2022-12-04T00:00:00 slug: advent-of-code-day-four tags: [advent-of-code-2022] --- **Spoilers for Advent of Code below** * [Day three](/post/advent-of-code-day-three) * [All Advent of Code posts](/blog/category/advent-of-code) * [Source](https://github.com/lewisdaleuk/advent-of-code-2022) Day four was probably the quickest for me to complete - but mostly because the other days had primed me pretty well for this kind of task and I'd been reading about using `HashSet` the night before. ## Part one Given set of range pairs, find the total number of pairs in which one is a subset of the other. So, given: ```bash 2-4,6-8 2-3,4-5 5-7,7-9 2-8,3-7 6-6,4-6 2-6,4-8 ``` I'd expect the following outputs: ```bash false false false true true false ``` The standard library makes it really easy to work with both ranges and structs, so first off I split and parse the ranges, and add them to HashSets: ```rust fn parse_range(range: &str) -> HashSet { let pieces: Vec = range.split('-').map(|r| r.parse::().unwrap()).collect(); (pieces[0] .. pieces[1]).collect() } fn overlaps(ranges: &str) -> bool { let (a, b) = range.split(',') .map(parse_range) .collect_tuple() .unwrap(); } ``` Then, I just check if `a` is a superset of `b`, or vice-versa: ```rust a.superset(&b) || b.superset(&a) ``` and tests pass! Seems to work. So I read my input file, and then filter it using this function: ```rust let outputs: Vec<&str> = input.split('\n').filter(cleaning::range_overlaps).collect(); println!("Overlaps: {}", overlaps); ``` ### Except... It doesn't. My answer was wrong - after a bit of investigation, I found an example that shouldn't have passed: ```bash 50-50,1-49 ``` It turns out that in Rust, ranges don't include the final index - so a range of `[50 - 50]` is actually an empty set, so any tests involving this would pass - because a set always contains an empty set. I updated my `parse_range` code to handle this: ```rust fn parse_range(range: &str) -> HashSet { let pieces: Vec = range.split('-').map(|r| r.parse::().unwrap()).collect(); if pieces[0] == pieces[1] { return HashSet::from([pieces[0]]); } (pieces[0] .. pieces[1] + 1).collect() } ``` and everything passes! On to Part Two ## Part two Now find the number of entries that simply overlap, rather than are subsets. This was a one-line change, because `HashSet` also has functions for this. I changed: ```rust a.superset(&b) || b.superset(&a) ``` to: ```rust !a.is_disjoint(&b) ``` And ran it, which turned out to be all that I needed. Day four complete, and if I remember to do tomorrow, I'll have beaten my own high score. ## Update After reading [this post from fasterthanli.me](https://fasterthanli.me/series/advent-of-code-2022/part-4), I've updated my parsing code to use `RangeInclusive` rather than just a `Range`. This means that I can get rid of my preemptive assertion, as well as the incremement on my range: ```rust fn parse_range(range: &str) -> HashSet { let pieces: Vec = range.split('-').map(|r| r.parse::().unwrap()).collect(); (pieces[0]..=pieces[1]).collect() } ```