Skip to content

Commit

Permalink
Day 5
Browse files Browse the repository at this point in the history
  • Loading branch information
kcaffrey committed Dec 5, 2023
1 parent b75713f commit fb340c8
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 5 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.

| Day | Part 1 | Part 2 |
| :---: | :---: | :---: |
| [Day 1](./src/bin/01.rs) | `38.2µs` | `81.2µs` |
| [Day 2](./src/bin/02.rs) | `43.7µs` | `43.2µs` |
| [Day 3](./src/bin/03.rs) | `115.3µs` | `113.3µs` |
| [Day 4](./src/bin/04.rs) | `52.3µs` | `55.3µs` |
| [Day 1](./src/bin/01.rs) | `32.6µs` | `80.4µs` |
| [Day 2](./src/bin/02.rs) | `44.6µs` | `43.7µs` |
| [Day 3](./src/bin/03.rs) | `114.5µs` | `112.6µs` |
| [Day 4](./src/bin/04.rs) | `52.2µs` | `55.8µs` |
| [Day 5](./src/bin/05.rs) | `23.4µs` | `41.3µs` |

**Total: 0.54ms**
**Total: 0.60ms**
<!--- benchmarking table --->

---
Expand Down
33 changes: 33 additions & 0 deletions data/examples/05.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
seeds: 79 14 55 13

seed-to-soil map:
50 98 2
52 50 48

soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15

fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4

water-to-light map:
88 18 7
18 25 70

light-to-temperature map:
45 77 23
81 45 19
68 64 13

temperature-to-humidity map:
0 69 1
1 0 69

humidity-to-location map:
60 56 37
56 93 4
258 changes: 258 additions & 0 deletions src/bin/05.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
use std::{ops::Range, str::FromStr};

advent_of_code::solution!(5);

pub fn part_one(input: &str) -> Option<u32> {
let almanac = input.parse::<Almanac>().expect("parse error");
almanac
.seeds
.iter()
.map(|&seed| almanac.lookup_location(seed) as u32)
.min()
}

pub fn part_two(input: &str) -> Option<u32> {
let almanac = input.parse::<Almanac>().expect("parse error");
almanac
.seeds
.chunks_exact(2)
.map(|chunk| chunk[0]..(chunk[0] + chunk[1]))
.filter_map(|r| almanac.min_location(r))
.map(|l| l as u32)
.min()
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct Almanac {
seeds: Vec<u64>,
seed_to_soil: AlmanacMap,
soil_to_fertilizer: AlmanacMap,
fertilizer_to_water: AlmanacMap,
water_to_light: AlmanacMap,
light_to_temperature: AlmanacMap,
temperature_to_humidity: AlmanacMap,
humidity_to_location: AlmanacMap,
}

impl Almanac {
pub fn lookup_location(&self, seed: u64) -> u64 {
let soil = self.seed_to_soil.map(seed);
let fertilizer = self.soil_to_fertilizer.map(soil);
let water = self.fertilizer_to_water.map(fertilizer);
let light = self.water_to_light.map(water);
let temperature = self.light_to_temperature.map(light);
let humidity = self.temperature_to_humidity.map(temperature);

self.humidity_to_location.map(humidity)
}

pub fn min_location(&self, input: Range<u64>) -> Option<u64> {
let mappings = [
&self.seed_to_soil,
&self.soil_to_fertilizer,
&self.fertilizer_to_water,
&self.water_to_light,
&self.light_to_temperature,
&self.temperature_to_humidity,
&self.humidity_to_location,
];
let mut ranges = vec![input];
let mut next_ranges = vec![];
for mapping in mappings {
for range in ranges.drain(..) {
next_ranges.extend(mapping.map_range(range));
}
std::mem::swap(&mut ranges, &mut next_ranges);
}
ranges.into_iter().filter_map(|r| r.min()).min()
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct AlmanacMap {
ranges: Vec<RangeMapping>,
}

impl AlmanacMap {
pub fn map(&self, input: u64) -> u64 {
self.ranges
.iter()
.find_map(|mapping| mapping.map(input))
.unwrap_or(input)
}

pub fn map_range(&self, input: Range<u64>) -> Vec<Range<u64>> {
let mut input = input;
let mut output = vec![];
for range_mapping in &self.ranges {
if range_mapping.source_range.start >= input.end {
output.push(input);
return output;
}
if input.start < range_mapping.source_range.start {
output.push(input.start..range_mapping.source_range.start);
}
let start = std::cmp::max(input.start, range_mapping.source_range.start);
let end = std::cmp::min(input.end, range_mapping.source_range.end);
if end > start {
output.push(
(start as i64 + range_mapping.offset) as u64
..(end as i64 + range_mapping.offset) as u64,
);
if end < input.end {
input = end..input.end;
} else {
return output;
}
}
}
output.push(input);
output
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct RangeMapping {
source_range: Range<u64>,
offset: i64,
}

impl RangeMapping {
pub fn map(&self, input: u64) -> Option<u64> {
if !self.source_range.contains(&input) {
return None;
}
Some(((input as i64) + self.offset) as u64)
}
}

#[derive(Debug)]
struct ParseAlmanacErr;

impl FromStr for Almanac {
type Err = ParseAlmanacErr;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split("\n\n").collect::<Vec<_>>();
if parts.len() != 8 {
return Err(ParseAlmanacErr);
}
let seeds = parts[0]
.split_once(':')
.ok_or(ParseAlmanacErr)?
.1
.split_whitespace()
.map(|s| s.parse::<u64>())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| ParseAlmanacErr)?;
let mut maps = parts
.into_iter()
.skip(1)
.map(|s| {
s.split_once(":\n")
.ok_or(ParseAlmanacErr)
.and_then(|(_, s)| s.parse::<AlmanacMap>())
})
.collect::<Result<Vec<_>, _>>()?
.into_iter();
Ok(Self {
seeds,
seed_to_soil: maps.next().ok_or(ParseAlmanacErr)?,
soil_to_fertilizer: maps.next().ok_or(ParseAlmanacErr)?,
fertilizer_to_water: maps.next().ok_or(ParseAlmanacErr)?,
water_to_light: maps.next().ok_or(ParseAlmanacErr)?,
light_to_temperature: maps.next().ok_or(ParseAlmanacErr)?,
temperature_to_humidity: maps.next().ok_or(ParseAlmanacErr)?,
humidity_to_location: maps.next().ok_or(ParseAlmanacErr)?,
})
}
}

impl FromStr for AlmanacMap {
type Err = ParseAlmanacErr;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut ranges = s
.lines()
.map(|line| line.parse::<RangeMapping>())
.collect::<Result<Vec<_>, _>>()?;
ranges.sort_by_key(|r| r.source_range.start);
Ok(Self { ranges })
}
}

impl FromStr for RangeMapping {
type Err = ParseAlmanacErr;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s
.split_whitespace()
.map(|s| s.parse::<u64>())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| ParseAlmanacErr)?;
if parts.len() != 3 {
return Err(ParseAlmanacErr);
}
Ok(Self {
source_range: parts[1]..(parts[1] + parts[2]),
offset: (parts[0] as i64) - (parts[1] as i64),
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_part_one() {
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(35));
}

#[test]
fn test_part_two() {
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(46));
}

#[test]
fn test_map_range() {
let almananc_map = AlmanacMap {
ranges: vec![
RangeMapping {
source_range: 50..98,
offset: 2,
},
RangeMapping {
source_range: 98..100,
offset: -48,
},
],
};
assert_eq!(almananc_map.map_range(79..93), vec![81..95]);
assert_eq!(almananc_map.map_range(90..100), vec![92..100, 50..52]);
assert_eq!(
almananc_map.map_range(90..102),
vec![92..100, 50..52, 100..102]
);

let almanac_map = AlmanacMap {
ranges: vec![
RangeMapping {
source_range: 0..15,
offset: 39,
},
RangeMapping {
source_range: 15..52,
offset: -15,
},
RangeMapping {
source_range: 52..54,
offset: -15,
},
],
};
assert_eq!(almanac_map.map_range(81..95), vec![81..95]);
}
}

0 comments on commit fb340c8

Please sign in to comment.