Welcome to day 4 of the Advent Of Code challenge.

Today is camp cleanup day. Elves need to clean up the area before unloading the ships.
The camp on the beach is organized into sections, each section
has a unique **ID number**, each elf is assigned a range of section IDs.

## Part 1

When the elves compare their section assignments with each other, they realize the assignments overlap. Given the following assignment pairs.

```
2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8
```

Each line consists of an assignment for a pair of elves, each elf is assigned a range of sections.
Reading the first line, the first elf has sections `2-4`

(2, 3, 4), the second has `6-8`

(6, 7, 8).
The example uses single digit section IDs for illustration, the given input file contains bigger section IDs.

Given the first pair `2-4,6-8`

the section ranges can be visualized as follows:

```
.234..... 2-4
.....678. 6-8
```

The sections do not overlap. Other assignments do overlap partially, while still others do overlap completey, for example pair `2-8,3-7`

```
.2345678. 2-8
..34567.. 3-7
```

which means the elf with the smaller assignment (`3-7`

) is completely contained by the first range already.

The task is to determine how many assignments exist where one section range fully contains the other?
The given sample input contains two assignments that fully overlap one of the ranges, `2-8,3-7`

& `6-6,4-6`

.

Let's start with the basic logic in `src/main.rs`

.

```
fn part1(sections: &[&str]) -> u32 {
todo!()
}
fn parse(input: &str) -> Vec<&str> {
todo!()
}
fn main() {
let input = parse(include_str!("input.txt"));
}
#[cfg(test)]
mod tests {
use crate::*;
const INPUT: &str = r#"
2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8
"#;
#[test]
fn check_part1() {
assert_eq!(2, part1(&parse(INPUT)));
}
}
```

After a bit of thought we want to determine that one range contains the other range.
This is when the bounds of one range are completely covered by the other bounds. Using the earlier
example pair `2-8,3-7`

:

```
.2345678. 2-8
..34567.. 3-7
```

The lower bound (`2`

) of the first range is smaller than the lower bound of the second (`3`

), while the upper bound (`8`

) is larger than
the second upper bound (`7`

). One idea is to simply check the range. There is a `RangeInclusive`

type in Rust, but let's define our own custom type for now.

```
#[derive(Debug)]
struct Range {
min: u32,
max: u32,
}
impl Range {
/// Constructor
pub fn new(min: u32, max: u32) -> Self {
Self {min, max}
}
/// Returns `true` if this Range contains the other completely.
pub fn contains(&self, rhs: &Self) -> bool {
self.min <= rhs.min && self.max >= rhs.max
}
}
#[cfg(test)]
mod tests {
// ...
#[test]
fn check_range_bounds() {
let l = Range::new(2, 8);
let r = Range::new(3, 7);
assert!(l.contains(&r));
assert!(!r.contains(&l));
}
}
```

The new `Range`

struct contains both bounds `min`

& `max`

as `u32`

. The constructor function `new`

helps to build a new `Range`

.
The method `contains`

checks if a given `Range`

is fully covered by the other.
The test `check_range_bounds`

in the test module checks the coverage for the assignment `2-8,3-7`

.

The next step is to parse the input text into a suitable list. For now the `parse`

function returns a `Vec<(Range, Range)>`

consisting of tuples with `Range`

.

```
fn parse(input: &str) -> Vec<(Range, Range)> {
input
.lines()
.map(str::trim)
.filter(|s| !s.is_empty())
.filter_map(|s| s.split_once(","))
.map(|(l, r)| (l.into(), r.into()))
.collect::<Vec<_>>()
}
```

There is some code missing. A good practice is to support to convert a `&str`

into the struct it represents. In this case we ignore error handling again (which is usually not recommended). To make the call of `l.into()`

work, an implementation of the `From`

trait is required. A better approach would to implement `TryFrom`

to recover from any wrong input string.

A naive implementation without error handling could look like:

```
impl From<&str> for Range {
fn from(range: &str) -> Self {
let (min, max) = range.split_once("-").expect("Failed to parse range.");
let min = min.parse::<u32>().expect("Not a number");
let max = max.parse::<u32>().expect("Not a number");
Range::new(min, max)
}
}
```

That parses the text `2-7`

into a `Range`

. It assumes the format is always `<min>-<max>`

where the first number is always lower than the second.
Thankfully the input text conforms to these expectations.

Having a tuple of two ranges in `(Range, Range)`

feels a bit inconvenient. It's often a good idea to use types that represent a concept, in our case
that is the assignment. Therefore a new type `Assignment`

is added that holds both sections / ranges.

```
struct Assignment {
/// First or left sections
pub l: Range,
/// Second or right sections
pub r: Range,
}
impl Assignment {
pub fn new(l: Range, r: Range) -> Self {
Self { l, r }
}
}
```

It's a simple struct with two fields and a constructor method. We should now have most of the logic to implement part 1 of the challenge.
We want to iterate overall assignments and see which assignments overlap, meaning where one section completely covers the other one of the same
assignment. We then filter all assignments for which this is true and count their occurrences. The next code also refactors the list of tuples
(`Vec<(Range, Range)>`

) into a list of assignments (`Vec<Assignment>`

).

```
fn part1(sections: &[Assignment]) -> usize {
sections
.iter()
.filter(|assignment| {
assignment.l.contains(&assignment.r) || assignment.r.contains(&assignment.l)
})
.count()
}
// Updated return type of function
fn parse(input: &str) -> Vec<Assignment> {
input
.lines()
.map(str::trim)
.filter(|s| !s.is_empty())
.filter_map(|s| s.split_once(","))
.map(|(l, r)| (l.into(), r.into()))
.map(|(l, r)| Assignment::new(l, r))
.collect::<Vec<_>>()
}
fn main() {
let input = parse(include_str!("input.txt"));
println!("Overlaps: {}", part1(&input));
}
```

The `part1`

function does not really use the `Assignment`

type yet, it mainly forwards to its inner fields.
Therfore the `map`

closure to determine the overlap feels as if it exposes some detail.
Let's move this check into the `Assignment`

type itself in a new function called `overlaps`

that returns a `bool`

.

```
impl Assignment {
fn overlaps(&self) -> bool {
self.l.contains(&self.r) || self.r.contains(&self.l)
}
}
```

Then the function `part1`

can be refactored to:

```
fn part1(sections: &[Assignment]) -> usize {
sections
.iter()
.filter(|assignment| assignment.overlaps()) // <-- updated
.count()
}
```

Running the program with `cargo run`

should provide the first answer.

## Part 2

The second part of the challenge is to also count all partially overlapping assignments. To find a partial overlap we have to find
if both ranges intersect with each other. Using one of the assignments from the sample data, e.g. `5-7,7-9`

the intersection looks
as follows.

```
....567.. 5-7
......789 7-9
^
```

They intersect on section `7`

. Coming up with a method to find the intersection between two ranges is not that difficult, fortunately
this is fairly well covered already, for example on this Computational Science board. Thankfully the boolean expression is simplified using boolean algebra
(applying *De Morgan's laws* to transform boolean expressions).

Let's add intersection methods to `Range`

and `Assignment`

.

```
impl Range {
pub fn intersect(&self, rhs: &Self) -> bool {
self.max >= rhs.min && rhs.max >= self.min
}
}
impl Assignment {
fn intersects(&self) -> bool {
self.l.intersect(&self.r)
}
}
```

The `Range::intersect`

determines if both ranges intersect with each other. The check in `Assignment::intersects`

is similar to `overlaps`

and checks if there is any intersection. Its calling `Range::intersect`

only once, because the other way around `self.r.intersect(&self.l)`

provides
the exact same result.

With that in place `part2`

will look then very similar to `part1`

, in addition to the overlap check it also tests if both ranges intersect.

```
fn part2(sections: &[Assignment]) -> usize {
sections
.iter()
.filter(|&assignment| assignment.intersects() || assignment.overlaps())
.count()
}
fn main() {
let input = parse(include_str!("input.txt"));
println!("Overlaps: {}", part1(&input));
println!("Intersections: {}", part2(&input));
}
```

The function returns the number of intersections, including all completely overlapping ones. With this in place `cargo run`

should output the correct total number now.