Last changes

This commit is contained in:
Kristofers Solo 2025-01-22 13:23:04 +02:00
parent d9596becdf
commit 20d6c6bff0
10 changed files with 454 additions and 126 deletions

15
2024/Cargo.lock generated
View File

@ -220,6 +220,21 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "day-08"
version = "0.1.0"
dependencies = [
"divan",
"itertools",
"miette",
"nom",
"rstest",
"test-log",
"thiserror 2.0.3",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "divan"
version = "0.1.16"

View File

@ -6,16 +6,10 @@ fn main() {
#[divan::bench]
fn part1() {
part1::process(divan::black_box(include_str!(
"../input1.txt",
)))
.unwrap();
part1::process(divan::black_box(include_str!("../input1.txt",))).unwrap();
}
#[divan::bench]
fn part2() {
part2::process(divan::black_box(include_str!(
"../input2.txt",
)))
.unwrap();
part2::process(divan::black_box(include_str!("../input2.txt",))).unwrap();
}

View File

@ -1,118 +0,0 @@
use miette::{Diagnostic, Result};
use regex::Regex;
use thiserror::Error;
#[derive(Error, Debug, Diagnostic)]
pub enum MultiplicationError {
#[error("Failed to parse first value: {0}")]
#[diagnostic(code(calculator::parse_error::first_value))]
FirstValueParseError(String),
#[error("Failed to parse second value: {0}")]
#[diagnostic(code(calculator::parse_error::second_value))]
SecondValueParseError(String),
#[error("Invalid multiplication format")]
#[diagnostic(code(calculator::parse_error::format))]
InvalidFormat,
}
#[derive(Debug)]
struct Multiplication(usize, usize);
impl Multiplication {
fn calculate(&self) -> usize {
self.0 * self.1
}
}
impl<T, U> TryFrom<(T, U)> for Multiplication
where
T: AsRef<str>,
U: AsRef<str>,
{
type Error = MultiplicationError;
fn try_from(value: (T, U)) -> std::result::Result<Self, Self::Error> {
let first =
value.0.as_ref().parse().map_err(|_| {
MultiplicationError::FirstValueParseError(value.0.as_ref().to_string())
});
let second =
value.1.as_ref().parse().map_err(|_| {
MultiplicationError::SecondValueParseError(value.1.as_ref().to_string())
});
Ok(Self(first?, second?))
}
}
#[derive(Error, Debug, Diagnostic)]
pub enum CalculatorError {
#[error("Failed to compile regex: {0}")]
#[diagnostic(code(calculator::regex_error))]
RegexError(#[from] regex::Error),
#[diagnostic(code(calculator::multiplication_error))]
#[error("Failed to parse multiplication: {0}")]
MultiplicationError(#[from] MultiplicationError),
#[error("Failed to process input: {0}")]
#[diagnostic(code(calculator::process_error))]
ProcessError(String),
}
#[derive(Debug)]
struct Calculator {
do_re: Regex,
mult_re: Regex,
}
impl Calculator {
fn new() -> Result<Self, CalculatorError> {
Ok(Self {
do_re: Regex::new(r"^.*?don't\(\)|do\(\)(.*?)don't\(\)|do\(\).*$")?,
mult_re: Regex::new(r"mul\((\d{1,3}),(\d{1,3})\)")?,
})
}
fn extract_multiplications<'a>(
&'a self,
line: &'a str,
) -> impl Iterator<Item = Multiplication> + 'a {
self.mult_re.captures_iter(line).filter_map(|caps| {
Multiplication::try_from((caps.get(1)?.as_str(), caps.get(2)?.as_str())).ok()
})
}
fn extract_do<'a>(&'a self, line: &'a str) -> impl Iterator<Item = &'a str> + 'a {
self.do_re
.captures_iter(line)
.filter_map(|caps| caps.get(0).map(|m| m.as_str()))
}
fn process(&self, input: &str) -> usize {
let value = input.lines().collect::<String>();
self.extract_do(&value)
.flat_map(|line| self.extract_multiplications(line))
.map(|mult| mult.calculate())
.sum()
}
}
#[tracing::instrument]
pub fn process(input: &str) -> Result<usize> {
let calculator = Calculator::new()?;
Ok(calculator.process(input))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process() -> Result<()> {
let input = "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))";
let result = 48;
assert_eq!(process(input)?, result);
Ok(())
}
}

28
2024/day-08/Cargo.toml Normal file
View File

@ -0,0 +1,28 @@
[package]
name = "day-08"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
itertools.workspace = true
nom.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
miette.workspace = true
thiserror.workspace = true
[dev-dependencies]
divan.workspace = true
rstest.workspace = true
test-log.workspace = true
[[bench]]
name = "day-08-bench"
path = "benches/benchmarks.rs"
harness = false
[lints.clippy]
pedantic = "warn"
nursery = "warn"

View File

@ -0,0 +1,21 @@
use day_08::*;
fn main() {
divan::main();
}
#[divan::bench]
fn part1() {
part1::process(divan::black_box(include_str!(
"../input1.txt",
)))
.unwrap();
}
#[divan::bench]
fn part2() {
part2::process(divan::black_box(include_str!(
"../input2.txt",
)))
.unwrap();
}

View File

@ -0,0 +1,12 @@
use day_08::part1::process;
use miette::{Context, Result};
#[tracing::instrument]
fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let file = include_str!("../../input1.txt");
let result = process(file).context("process part 1")?;
println!("{result}");
Ok(())
}

View File

@ -0,0 +1,12 @@
use day_08::part2::process;
use miette::{Context, Result};
#[tracing::instrument]
fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let file = include_str!("../../input2.txt");
let result = process(file).context("process part 2")?;
println!("{result}");
Ok(())
}

2
2024/day-08/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod part1;
pub mod part2;

341
2024/day-08/src/part1.rs Normal file
View File

@ -0,0 +1,341 @@
use std::{
collections::HashMap,
fmt::Display,
ops::{Add, Sub},
str::FromStr,
};
use miette::{Diagnostic, Result};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Vec2 {
row: usize,
col: usize,
}
impl Vec2 {
const fn abs_diff(self, other: Self) -> Self {
Self {
row: self.row.abs_diff(other.row),
col: self.col.abs_diff(other.col),
}
}
const fn manhattan_distance(self, other: Self) -> usize {
let diff = self.abs_diff(other);
diff.row + diff.col
}
}
impl From<Vec2> for (usize, usize) {
fn from(value: Vec2) -> Self {
(value.row, value.col)
}
}
impl Add for Vec2 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
row: self.row + rhs.row,
col: self.col + rhs.col,
}
}
}
impl Sub for Vec2 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Vec2 {
row: self.row.saturating_sub(rhs.row),
col: self.col.saturating_sub(rhs.col),
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum Location {
Antenna(char),
Antinode,
Empty,
}
impl From<char> for Location {
fn from(value: char) -> Self {
match value {
'.' => Self::Empty,
'#' => Self::Antinode,
ch => Self::Antenna(ch),
}
}
}
impl Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ch = match self {
Self::Antenna(ch) => *ch,
Self::Antinode => '#',
Self::Empty => '.',
};
write!(f, "{}", ch)
}
}
#[derive(Debug, Error, Diagnostic)]
enum RoofError {
#[error("Failed to parse roof")]
ParseError,
}
fn calculate_diffs(positions: &[Vec2]) -> Vec<Vec2> {
positions
.iter()
.enumerate()
.flat_map(|(idx, &pos1)| {
positions[idx + 1..]
.iter()
.map(move |&pos2| pos1.abs_diff(pos2))
})
.collect()
}
#[derive(Debug, PartialEq, Eq)]
struct Roof(Vec<Vec<Location>>);
impl Roof {
fn place_antinodes(&mut self) {
let antenna_positions = self.antenna_positions();
for (_, pos) in antenna_positions.iter() {
for i in 0..pos.len() {
for j in (i + 1)..pos.len() {
let (pos1, pos2) = (pos[i], pos[j]);
if let Some(antinode_pos) = self.find_antinode_position(pos1, pos2) {
self.0[antinode_pos.row][antinode_pos.col] = Location::Antinode;
}
}
}
}
}
fn find_antinode_position(&self, pos1: Vec2, pos2: Vec2) -> Option<Vec2> {
let diff = pos1.abs_diff(pos2);
let total_dist = diff.row + diff.col;
// Try each step between the antennas
for step in 0..=total_dist {
let row = if pos1.row < pos2.row {
pos1.row + step.min(diff.row)
} else {
pos1.row - step.min(diff.row)
};
let col = if pos1.col < pos2.col {
pos1.col + step.min(diff.col)
} else {
pos1.col - step.min(diff.col)
};
let candidate = Vec2 { row, col };
// Check if position is valid and equidistant
if row < self.0.len()
&& col < self.0[0].len()
&& matches!(self.0[row][col], Location::Empty)
{
let dist1 = pos1.manhattan_distance(candidate);
let dist2 = pos2.manhattan_distance(candidate);
if dist1 == dist2 {
return Some(candidate);
}
}
}
None
}
fn antenna_positions(&self) -> HashMap<char, Vec<Vec2>> {
self.0
.iter()
.enumerate()
.flat_map(|(row_idx, row)| {
row.iter()
.enumerate()
.filter_map(move |(col_idx, col)| match col {
Location::Antenna(ch) => Some((
*ch,
Vec2 {
row: row_idx,
col: col_idx,
},
)),
_ => None,
})
})
.fold(HashMap::new(), |mut acc, (key, pos)| {
acc.entry(key).or_default().push(pos);
acc
})
}
fn count(&self) -> usize {
self.0
.iter()
.map(|row| {
row.iter()
.filter(|pos| matches!(pos, Location::Antinode))
.count()
})
.sum()
}
fn row_len(&self) -> usize {
self.0.len()
}
fn col_len(&self) -> usize {
self.0[0].len()
}
fn is_within_bounds(&self, pos: &Vec2) -> bool {
pos.row < self.row_len() && pos.col < self.col_len()
}
}
impl FromStr for Roof {
type Err = RoofError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let roof = s
.lines()
.map(|line| line.chars().map(Location::from).collect::<Vec<_>>())
.collect::<Vec<_>>();
Ok(Self(roof))
}
}
impl Display for Roof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (idx, row) in self.0.iter().enumerate() {
for pos in row {
write!(f, "{}", pos)?;
}
if idx < self.0.len() - 1 {
writeln!(f)?;
}
}
Ok(())
}
}
#[tracing::instrument]
pub fn process(input: &str) -> Result<usize> {
let mut roof = Roof::from_str(input)?;
roof.place_antinodes();
println!("{roof}");
Ok(roof.count())
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[test]
fn test_process() -> Result<()> {
let input = "............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............";
let result = 14;
assert_eq!(process(input)?, result);
Ok(())
}
#[rstest]
#[case(
"..........
..........
..........
....a.....
..........
.....a....
..........
..........
..........
..........",
"..........
...#......
..........
....a.....
..........
.....a....
..........
......#...
..........
.........."
)]
/* #[case(
"..........
..........
..........
....a.....
........a.
.....a....
..........
..........
..........
..........",
"..........
...#......
#.........
....a.....
........a.
.....a....
..#.......
......#...
..........
.........."
)]
#[case(
"............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............",
"......#....#
...#....0...
....#0....#.
..#....0....
....0....#..
.#....A.....
...#........
#......#....
........A...
.........A..
..........#.
..........#."
)] */
fn test_layout(#[case] input_str: &str, #[case] output_str: &str) -> Result<()> {
let mut input = Roof::from_str(input_str)?;
input.place_antinodes();
let output = Roof::from_str(output_str)?;
assert_eq!(input, output);
Ok(())
}
}

21
2024/day-08/src/part2.rs Normal file
View File

@ -0,0 +1,21 @@
use miette::Result;
#[tracing::instrument]
pub fn process(input: &str) -> Result<usize> {
todo!("day xx - part 2");
Ok(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process() -> Result<()> {
let input = "";
todo!("haven't built test yet");
let result = 0;
assert_eq!(process(input)?, result);
Ok(())
}
}