Add day 5 solution

master
Nick Krichevsky 2021-12-05 01:57:00 -05:00
parent 59892c3d69
commit 217350a7a8
3 changed files with 273 additions and 0 deletions

95
day5/Cargo.lock generated Normal file
View File

@ -0,0 +1,95 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "day5"
version = "0.1.0"
dependencies = [
"nom",
"thiserror",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"

10
day5/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "day5"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nom = "7.1"
thiserror = "1.0"

168
day5/src/main.rs Normal file
View File

@ -0,0 +1,168 @@
#![warn(clippy::all, clippy::pedantic)]
use std::cmp;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader};
use nom::bytes::complete::tag;
use nom::combinator::eof;
use nom::sequence::terminated;
use nom::{
bytes::complete::take_while1, character::complete::char, combinator::map_res,
sequence::separated_pair, IResult,
};
use thiserror::Error;
#[derive(Debug, Error)]
enum Error {
#[error("The coordinates {0:?} and {0:?} are not in line with the given strategy {:0?}")]
InvalidDirection(Coordinate, Coordinate, Strategy),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Coordinate(u32, u32);
#[derive(Debug, Clone, Copy)]
enum Strategy {
OrthogonalOnly,
OrthogonalAnd45Degrees,
}
impl Strategy {
fn points_follow_strategy(self, a: Coordinate, b: Coordinate) -> bool {
let x_range = order_pair(a.0, b.0);
let y_range = order_pair(a.1, b.1);
let rise = y_range.1 - y_range.0;
let run = x_range.1 - x_range.0;
match self {
Self::OrthogonalOnly => run == 0 || rise == 0,
Self::OrthogonalAnd45Degrees => run == 0 || rise == 0 || run == rise,
}
}
}
impl Coordinate {
/// Create an iterator that will move between this point and a given ending point.
///
/// # Errors
/// If the direction between this point and the other are not in a direction
/// that matches the given strategy, [`Error::InvalidDirection`] is returned
fn iter_between(
self,
other: Coordinate,
strategy: Strategy,
) -> Result<impl Iterator<Item = Coordinate>, Error> {
if !strategy.points_follow_strategy(self, other) {
return Err(Error::InvalidDirection(self, other, strategy));
}
let x_range = order_pair(self.0, other.0);
let y_range = order_pair(self.1, other.1);
// Calculate the distance between points using the "max norm"
// this is like the manhattan distance, but diagonals are 1
// (I'm effectively using it as an integral euclidian distance given the context)
let travel_distance = cmp::max(x_range.1 - x_range.0, y_range.1 - y_range.0);
let iter = (0..=travel_distance).map(move |n| {
let end_x = add_to_component_directionally(self.0, other.0, n);
let end_y = add_to_component_directionally(self.1, other.1, n);
Coordinate(end_x, end_y)
});
Ok(iter)
}
}
fn order_pair<T: PartialOrd>(a: T, b: T) -> (T, T) {
if a > b {
(b, a)
} else {
(a, b)
}
}
/// Add some number, n, to the start component of a vector, in the direction of its ending point.
fn add_to_component_directionally(start: u32, end: u32, n: u32) -> u32 {
match start.cmp(&end) {
cmp::Ordering::Equal => start,
cmp::Ordering::Greater => start - n,
cmp::Ordering::Less => start + n,
}
}
/// Build a map of the number of intersections between lines bounded (inclusively) by each element
/// the `coordinate_pairs` slice. The retruend map will indicate the number of (non-zero) interactions
/// at each point
fn build_intersection_count_map(
coordinate_pairs: &[(Coordinate, Coordinate)],
strategy: Strategy,
) -> Result<HashMap<Coordinate, u32>, Error> {
let mut counts = HashMap::new();
for &pair in coordinate_pairs {
let (start, end) = pair;
let iter_res = start.iter_between(end, strategy);
if let Err(Error::InvalidDirection(_, _, _)) = iter_res {
continue;
}
for coord in iter_res? {
let count = counts.get(&coord).unwrap_or(&0);
let updated_count = count + 1;
counts.insert(coord, updated_count);
}
}
Ok(counts)
}
fn part1(coordinate_pairs: &[(Coordinate, Coordinate)]) -> usize {
let map = build_intersection_count_map(coordinate_pairs, Strategy::OrthogonalOnly)
.expect("Failed to build coordinate map from input");
map.values().filter(|&&n| n >= 2).count()
}
fn part2(coordinate_pairs: &[(Coordinate, Coordinate)]) -> usize {
let map = build_intersection_count_map(coordinate_pairs, Strategy::OrthogonalAnd45Degrees)
.expect("Failed to build coordinate map from input");
map.values().filter(|&&n| n >= 2).count()
}
fn parse_number(s: &str) -> IResult<&str, u32> {
map_res(take_while1(|c: char| c.is_ascii_digit()), str::parse)(s)
}
fn parse_coordinate(s: &str) -> IResult<&str, Coordinate> {
let (remaining, parsed_numbers) = separated_pair(parse_number, char(','), parse_number)(s)?;
Ok((remaining, Coordinate(parsed_numbers.0, parsed_numbers.1)))
}
fn parse_line(line: &str) -> IResult<&str, (Coordinate, Coordinate)> {
terminated(
separated_pair(parse_coordinate, tag(" -> "), parse_coordinate),
eof,
)(line)
}
fn main() {
let input_file_name = env::args().nth(1).expect("No input filename specified");
let input_file = File::open(input_file_name).expect("Could not open input file");
let input_coordinates = BufReader::new(input_file)
.lines()
.map(|res| res.expect("Failed to read line"))
.map(|s| {
let (_, coords) = parse_line(&s).expect("Failed to read line");
coords
})
.collect::<Vec<_>>();
println!("Part 1: {}", part1(&input_coordinates));
println!("Part 2: {}", part2(&input_coordinates));
}