Compare commits

..

3 Commits

Author SHA1 Message Date
67f4cb57ed
day20: part 2 solution (slow)
Some checks failed
test / AoC 2024 (push) Failing after 3m36s
2024-12-21 22:45:59 -08:00
c433076f40
day22: part 2 solution. 38s runtime but works. 2024-12-21 21:58:09 -08:00
52e7bb9af7
day22: part 1 solution
Some checks failed
test / AoC 2024 (push) Failing after 4m11s
2024-12-21 21:16:04 -08:00
4 changed files with 79 additions and 85 deletions

View File

@ -1,33 +1,36 @@
use aoc_runner_derive::aoc;
use grid::{AsCoord2d, Coord2d, Grid};
use itertools::Itertools;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::collections::VecDeque; use std::collections::VecDeque;
use aoc_runner_derive::aoc;
use grid::Grid;
struct RaceTrack { struct RaceTrack {
map: Grid<u8>, map: Grid<u8>,
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
struct State { struct State {
pos: Coord2d, pos: (i64, i64),
cost: u64, cost: u64,
} }
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct CheatState { struct CheatState {
s: State, s: State,
p: Vec<(i64, i64)>,
} }
const DIRECTIONS: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)]; const DIRECTIONS: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
impl RaceTrack { impl RaceTrack {
fn valid_moves<'a>(&'a self, CheatState { s: state }: &'a CheatState) -> impl Iterator<Item = CheatState> + 'a { fn valid_moves<'a>(&'a self, CheatState { s: state, p }: &'a CheatState) -> impl Iterator<Item = CheatState> + 'a {
let mut new_path = p.clone();
new_path.push(state.pos);
DIRECTIONS DIRECTIONS
.iter() .iter()
.map(|dir| state.pos + dir) .map(|dir| (state.pos.0 + dir.0, state.pos.1 + dir.1))
.filter_map(move |pos| match &self.map.get(&pos) { .filter_map(move |pos| match &self.map.get(&pos) {
Some(b'.') | Some(b'S') | Some(b'E') => Some(CheatState { Some(b'.') | Some(b'S') | Some(b'E') => Some(CheatState {
p: new_path.clone(),
s: State { s: State {
pos, pos,
cost: state.cost + 1, cost: state.cost + 1,
@ -36,19 +39,22 @@ impl RaceTrack {
_ => None, _ => None,
}) })
} }
fn path_costs(&self, start: Coord2d, goal: Coord2d) -> Grid<Option<u64>> { fn path_costs(&self, start: (i64, i64), goal: (i64, i64)) -> (Vec<(i64, i64)>, Grid<Option<u64>>) {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut visited = self.map.same_shape(None); let mut visited = self.map.same_shape(None);
let start_state = CheatState { let start_state = CheatState {
s: State { pos: start, cost: 0 }, s: State { pos: start, cost: 0 },
p: Vec::new(),
}; };
visited.set(&start, Some(0)); visited.set(&start, Some(0));
queue.push_back(start_state); queue.push_back(start_state);
while let Some(state) = queue.pop_front() { while let Some(state) = queue.pop_front() {
if state.s.pos == goal { if state.s.pos == goal {
return visited; let mut final_path = state.p;
final_path.push(goal);
return (final_path, visited);
} }
let moves = self.valid_moves(&state); let moves = self.valid_moves(&state);
@ -63,49 +69,70 @@ impl RaceTrack {
panic!("no path"); panic!("no path");
} }
fn find_cheats(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, min: u64) -> i64 { fn find_cheats(
let mut n = 0; &self,
path: &Vec<(i64, i64)>,
costs: &Grid<Option<u64>>,
min: u64,
) -> Vec<((i64, i64), (i64, i64), u64)> {
let mut cheats = Vec::new();
for pos in path { for pos in path {
let local_cost = costs.get(pos).unwrap().unwrap(); let local_cost = costs.get(pos).unwrap().unwrap();
for ofs in DIRECTIONS { for ofs in DIRECTIONS {
let cheat_exit = (pos.x() + ofs.0 * 2, pos.y() + ofs.1 * 2); let cheat_start = (pos.0 + ofs.0, pos.1 + ofs.1);
let cheat_exit = (pos.0 + ofs.0 * 2, pos.1 + ofs.1 * 2);
if let Some(Some(cheat_cost)) = costs.get(&cheat_exit) { if let Some(Some(cheat_cost)) = costs.get(&cheat_exit) {
if *cheat_cost > local_cost + 2 { if *cheat_cost > local_cost + 2 {
let cheat_savings = cheat_cost - local_cost - 2; let cheat_savings = cheat_cost - local_cost - 2;
if cheat_savings >= min { if cheat_savings >= min {
n += 1; cheats.push((cheat_start, cheat_exit, cheat_savings));
} }
} }
} }
} }
} }
n cheats
} }
fn taxi_dist<A: AsCoord2d, B: AsCoord2d>(from: &A, to: &B) -> u64 { fn taxi_dist(&self, from: &(i64, i64), to: &(i64, i64)) -> Option<u64> {
from.x().abs_diff(to.x()) + from.y().abs_diff(to.y()) if self.map.is_valid(to) {
Some(from.0.abs_diff(to.0) + from.1.abs_diff(to.1))
} else {
None
}
} }
fn find_cheats_n(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, max_length: u64, min: u64) -> i64 { fn find_cheats_n(
path.par_iter() &self,
.map_with(costs, |costs, pos| { path: &Vec<(i64, i64)>,
costs: &Grid<Option<u64>>,
max_length: u64,
min: u64,
) -> Vec<((i64, i64), (i64, i64))> {
let mut cheats = Vec::new();
let mut solutions = self.map.clone();
for pos in path {
let from_cost = costs.get(pos).unwrap().unwrap(); let from_cost = costs.get(pos).unwrap().unwrap();
let mut n = 0; for x in 0..self.map.width() as i64 {
for x in pos.x - max_length as i64 - 1..=pos.x + max_length as i64 { for y in 0..self.map.height() as i64 {
for y in pos.y - max_length as i64 - 1..=pos.y + max_length as i64 { if let Some(dist) = self.taxi_dist(pos, &(x, y)) {
let dist = Self::taxi_dist(pos, &(x, y));
if dist <= max_length && dist >= 2 { if dist <= max_length && dist >= 2 {
if let Some(Some(to_cost)) = costs.get(&(x, y)) { if let Some(to_cost) = costs.get(&(x, y)).unwrap() {
solutions.set(pos, b'O');
solutions.set(&(x, y), b'O');
if *to_cost > (from_cost + dist) && (to_cost - (from_cost + dist) >= min) { if *to_cost > (from_cost + dist) && (to_cost - (from_cost + dist) >= min) {
n += 1; cheats.push((*pos, (x, y)));
} }
} }
} }
} }
solutions.set(&(x, y), *self.map.get(&(x, y)).unwrap());
} }
n }
}) solutions.set(pos, *self.map.get(pos).unwrap());
.sum() }
cheats
} }
} }
@ -118,30 +145,20 @@ fn part1_impl(input: &str, cheat_min: u64) -> i64 {
let track = parse(input); let track = parse(input);
let start = track.map.find(&b'S').unwrap(); let start = track.map.find(&b'S').unwrap();
let goal = track.map.find(&b'E').unwrap(); let goal = track.map.find(&b'E').unwrap();
let costs = track.path_costs(start, goal); let (best_path, costs) = track.path_costs(start.into(), goal.into());
let path_squares = costs let cheats = track.find_cheats(&best_path, &costs, cheat_min);
.data
.iter() cheats.len() as i64
.enumerate()
.filter(|(_i, c)| c.is_some())
.filter_map(|(i, _)| track.map.coord(i as i64))
.collect_vec();
track.find_cheats(&path_squares, &costs, cheat_min)
} }
fn part2_impl(input: &str, max_length: u64, cheat_min: u64) -> i64 { fn part2_impl(input: &str, max_length: u64, cheat_min: u64) -> i64 {
let track = parse(input); let track = parse(input);
let start = track.map.find(&b'S').unwrap(); let start = track.map.find(&b'S').unwrap();
let goal = track.map.find(&b'E').unwrap(); let goal = track.map.find(&b'E').unwrap();
let costs = track.path_costs(start, goal); let (best_path, costs) = track.path_costs(start.into(), goal.into());
let path_squares = costs let cheats = track.find_cheats_n(&best_path, &costs, max_length, cheat_min);
.data
.iter() cheats.len() as i64
.enumerate()
.filter(|(_i, c)| c.is_some())
.filter_map(|(i, _)| track.map.coord(i as i64))
.collect_vec();
track.find_cheats_n(&path_squares, &costs, max_length, cheat_min)
} }
#[aoc(day20, part1)] #[aoc(day20, part1)]

View File

@ -1,4 +1,4 @@
use aoc_runner_derive::aoc; use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools; use itertools::Itertools;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
@ -31,7 +31,7 @@ fn prices(mut secret: i64, n: i64) -> Vec<i8> {
prices prices
} }
fn changes(prices: &[i8]) -> Vec<Change> { fn changes(prices: &Vec<i8>) -> Vec<Change> {
prices prices
.windows(2) .windows(2)
.map(|a| Change { .map(|a| Change {
@ -45,10 +45,14 @@ fn profit_for_sequence(changes: &Vec<Vec<Change>>, seq: &[i8]) -> i64 {
changes changes
.par_iter() .par_iter()
.filter_map(|inner| { .filter_map(|inner| {
inner if let Some(buy) = inner
.windows(seq.len()) .windows(seq.len())
.find(|window| window.iter().zip(seq).all(|(w, z)| w.delta == *z)) .find(|window| window.iter().zip(seq).all(|(w, z)| w.delta == *z))
.map(|buy| buy[seq.len() - 1].price as i64) {
Some(buy[seq.len() - 1].price as i64)
} else {
None
}
}) })
.sum() .sum()
} }
@ -56,7 +60,7 @@ fn profit_for_sequence(changes: &Vec<Vec<Change>>, seq: &[i8]) -> i64 {
fn find_best_sequence(changes: &Vec<Vec<Change>>) -> [i8; 4] { fn find_best_sequence(changes: &Vec<Vec<Change>>) -> [i8; 4] {
let mut best_seq = [0, 0, 0, 0]; let mut best_seq = [0, 0, 0, 0];
let mut best_profit = 0; let mut best_profit = 0;
for seq in (0..4).map(|_| (-9..=9i8)).multi_cartesian_product() { for seq in (0..4).map(|_| (-9..=9 as i8)).multi_cartesian_product() {
let profit = profit_for_sequence(changes, &seq); let profit = profit_for_sequence(changes, &seq);
if profit > best_profit { if profit > best_profit {
best_seq = seq.try_into().unwrap(); best_seq = seq.try_into().unwrap();
@ -71,14 +75,14 @@ fn parse(input: &str) -> Vec<i64> {
} }
#[aoc(day22, part1)] #[aoc(day22, part1)]
pub fn part1(input: &str) -> i64 { fn part1(input: &str) -> i64 {
let secrets = parse(input); let secrets = parse(input);
secrets.iter().map(|s| rounds(*s, 2000)).sum::<i64>() secrets.iter().map(|s| rounds(*s, 2000)).sum::<i64>()
} }
#[aoc(day22, part2)] #[aoc(day22, part2)]
pub fn part2(input: &str) -> i64 { fn part2(input: &str) -> i64 {
let secrets = parse(input); let secrets = parse(input);
let price_changes = secrets.iter().map(|s| changes(&prices(*s, 2000))).collect_vec(); let price_changes = secrets.iter().map(|s| changes(&prices(*s, 2000))).collect_vec();
@ -128,7 +132,10 @@ mod tests {
let secrets = parse(EXAMPLE2); let secrets = parse(EXAMPLE2);
let price_changes = secrets.iter().map(|s| changes(&prices(*s, 2000))).collect_vec(); let price_changes = secrets.iter().map(|s| changes(&prices(*s, 2000))).collect_vec();
assert_eq!(profit_for_sequence(&price_changes, &[-2, 1, -1, 3]), 23); assert_eq!(
profit_for_sequence(&price_changes, &[-2, 1, -1, 3]),
23
);
} }
#[test] #[test]

View File

@ -12,7 +12,6 @@ pub mod day19;
pub mod day2; pub mod day2;
pub mod day20; pub mod day20;
pub mod day21; pub mod day21;
pub mod day22;
pub mod day3; pub mod day3;
pub mod day4; pub mod day4;
pub mod day5; pub mod day5;

View File

@ -39,16 +39,6 @@ impl<T: AsCoord2d> Add<T> for &Coord2d {
} }
} }
impl<T: AsCoord2d> Add<&T> for Coord2d {
type Output = Coord2d;
fn add(self, rhs: &T) -> Self::Output {
Coord2d {
x: self.x() + rhs.x(),
y: self.y() + rhs.y(),
}
}
}
impl AsCoord2d for Coord2d { impl AsCoord2d for Coord2d {
fn to_coord(self) -> Coord2d { fn to_coord(self) -> Coord2d {
self self
@ -92,25 +82,6 @@ where
} }
} }
impl<T> AsCoord2d for &(T, T)
where
T: Copy + TryInto<i64>,
<T as TryInto<i64>>::Error: Debug,
{
fn to_coord(self) -> Coord2d {
Coord2d {
x: self.0.try_into().unwrap(),
y: self.1.try_into().unwrap(),
}
}
fn x(&self) -> i64 {
self.0.try_into().unwrap()
}
fn y(&self) -> i64 {
self.1.try_into().unwrap()
}
}
impl From<Coord2d> for (i64, i64) { impl From<Coord2d> for (i64, i64) {
fn from(value: Coord2d) -> Self { fn from(value: Coord2d) -> Self {
(value.x, value.y) (value.x, value.y)