Add day 5 solution
parent
59892c3d69
commit
217350a7a8
|
@ -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"
|
|
@ -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"
|
|
@ -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));
|
||||
}
|
Loading…
Reference in New Issue