Skip to content

Commit

Permalink
solve: day20
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-anders committed Dec 22, 2024
1 parent 78bc780 commit e5aa40c
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 72 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
resolver = "2"
members = [
"utils",
"aoc_derive", "day1", "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9", "day10", "day11", "day12", "day13", "day14", "day15", "day16", "day17", "day18", "day19",
"aoc_derive", "day1", "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9", "day10", "day11", "day12", "day13", "day14", "day15", "day16", "day17", "day18", "day19", "day20", "day21",
]

[workspace.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion day12/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn solve(input: Input) -> impl Into<Solution> {
let regions = region_map.entry(*c).or_default();

if !regions.iter().any(|r| r.contains(&pos)) {
regions.push(floodfill(&map, pos).into_iter().collect());
regions.push(floodfill(&map, pos).keys().copied().collect())
}
}

Expand Down
18 changes: 18 additions & 0 deletions day20/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "day20"
version = "0.1.0"
edition = "2024"

[dependencies]
aoc_derive.path = '../aoc_derive'
utils.path = '../utils'
derive_more.workspace = true
itertools.workspace = true
lazy-regex.workspace = true
parse-display.workspace = true
rayon.workspace = true
regex.workspace = true
num.workspace = true

[dev-dependencies]
pretty_assertions.workspace = true
106 changes: 106 additions & 0 deletions day20/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use aoc_derive::aoc_main;
use derive_more::derive::{Deref, DerefMut, From};
use graphs::{UnweightedGraph, bfs, floodfill};
use grid::Grid;
use math::Vec2D;
use utils::*;

#[derive(Debug, Clone, From, Deref, DerefMut)]
struct Racetrack(Grid<char>);

impl UnweightedGraph for Racetrack {
type Node = Vec2D;

fn neighbors<'a, 'b: 'a>(&'a self, node: &'b Vec2D) -> impl Iterator<Item = Vec2D> + 'a {
self.orthogonal_neighbors(node).filter(|&n| self[n] != '#')
}
}

#[aoc_main(100)]
fn solve(input: Input, min_save: usize) -> impl Into<Solution> {
let track: Racetrack = input.char_grid().into();
let start = track.find_position(&'S').unwrap();
let end = track.find_position(&'E').unwrap();

let best_without_cheat = bfs(&track, start, end).distance.unwrap();

let distance_from_start = floodfill(&track, start);
let distance_from_end = floodfill(&track, end);

let find_cheats = |pos: Vec2D, max_cheat: usize| {
track
.iter()
.filter_map(|(pos_after_cheat, &c)| {
let dist = (pos - pos_after_cheat).manhattan_dist();
(c != '#' && dist <= max_cheat).then_some((pos_after_cheat, dist))
})
.filter(|&(pos_after_cheat, dist)| {
let total_dist =
distance_from_start[&pos] + dist + distance_from_end[&pos_after_cheat];
total_dist < best_without_cheat && best_without_cheat - total_dist >= min_save
})
.count()
};

(
track.iter().filter(|&(_, &c)| c != '#').map(|(pos, _)| find_cheats(pos, 2)).sum_usize(),
track.iter().filter(|&(_, &c)| c != '#').map(|(pos, _)| find_cheats(pos, 20)).sum_usize(),
)
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
assert_eq!(
solve(
r#"###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############"#
.into(),
0
)
.into()
.part1,
Some((14 + 14 + 2 + 4 + 2 + 3 + 5).to_string())
);

assert_eq!(
solve(
r#"###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############"#
.into(),
50
)
.into()
.part2,
Some((32 + 31 + 29 + 39 + 25 + 23 + 20 + 19 + 12 + 14 + 12 + 22 + 4 + 3).to_string())
);
}
}
18 changes: 18 additions & 0 deletions day21/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "day21"
version = "0.1.0"
edition = "2024"

[dependencies]
aoc_derive.path = '../aoc_derive'
utils.path = '../utils'
derive_more.workspace = true
itertools.workspace = true
lazy-regex.workspace = true
parse-display.workspace = true
rayon.workspace = true
regex.workspace = true
num.workspace = true

[dev-dependencies]
pretty_assertions.workspace = true
232 changes: 232 additions & 0 deletions day21/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use std::{
collections::{BTreeMap, HashMap},
iter::{once, repeat, repeat_n},
};

use aoc_derive::aoc_main;
use graphs::{WeightedGraph, dijkstra};
use itertools::{Itertools, iproduct};
use math::Vec2D;
use utils::*;

#[derive(Debug, Clone)]
struct KeypadRobot {
pos: Vec2D,
num_dirpads: usize,
}

fn check_path(start: Vec2D, path: &[char], forbidden: Vec2D) -> bool {
path.iter()
.try_fold(start, |pos, c| {
(pos != forbidden).then_some(
pos + Vec2D::from(match c {
'<' => (-1, 0),
'>' => (1, 0),
'^' => (0, -1),
'v' => (0, 1),
_ => unreachable!(),
}),
)
})
.is_some()
}

impl KeypadRobot {
fn new(num_dirpads: usize) -> Self {
Self { pos: Self::coord('A'), num_dirpads }
}

fn coord(c: char) -> Vec2D {
match c {
'7' => (0, 0),
'8' => (1, 0),
'9' => (2, 0),
'4' => (0, 1),
'5' => (1, 1),
'6' => (2, 1),
'1' => (0, 2),
'2' => (1, 2),
'3' => (2, 2),
'0' => (1, 3),
'A' => (2, 3),
_ => unreachable!(),
}
.into()
}

fn expand_move(&self, from: char, to: char) -> usize {
(0..self.num_dirpads)
.fold([(make_move(from, to), 1)].into_iter().collect(), |path, _| expand_path(path))
.into_iter()
.map(|(path, count)| path.len() * count)
.sum()
}

fn enter_char(&mut self, c: char) -> usize {
let dist =
dijkstra(self, [(self.pos, 'A', false)], |&node| node == (Self::coord(c), 'A', true));

self.pos = Self::coord(c);
dist.unwrap()
}

fn enter_code(&mut self, code: &str) -> usize {
code.chars().map(|c| self.enter_char(c)).sum()
}
}

impl WeightedGraph for KeypadRobot {
type Node = (Vec2D, char, bool);

fn neighbors<'a, 'b: 'a>(
&'a self,
&(pos, keypad_pos, has_pushed): &'b Self::Node,
) -> impl Iterator<Item = (Self::Node, graphs::Cost)> + 'a {
if has_pushed {
vec![].into_iter()
} else {
[
Some(((pos, 'A', true), self.expand_move(keypad_pos, 'A'))),
(pos.x < 2)
.then_some(((pos + (1, 0), '>', false), self.expand_move(keypad_pos, '>'))),
(pos.x > 0 && pos != (1, 3))
.then_some(((pos + (-1, 0), '<', false), self.expand_move(keypad_pos, '<'))),
(pos.y < 3 && pos != (0, 2))
.then_some(((pos + (0, 1), 'v', false), self.expand_move(keypad_pos, 'v'))),
(pos.y > 0)
.then_some(((pos + (0, -1), '^', false), self.expand_move(keypad_pos, '^'))),
]
.into_iter()
.flatten()
.collect_vec()
.into_iter()
}
}
}

fn shortest_sequence(code: &str, num_dirpads: usize) -> usize {
KeypadRobot::new(num_dirpads).enter_code(code)
}

fn complexity(code: &str, num_dirpads: usize) -> usize {
println!("check code {code}");
code[..3].parse::<usize>().unwrap() * shortest_sequence(code, num_dirpads)
}

#[aoc_main]
fn solve(input: Input) -> impl Into<Solution> {
(
input.lines().map(|code| complexity(code, 1)).sum_usize(),
input.lines().map(|code| complexity(code, 24)).sum_usize(),
)
}

// 454307058698260
// 1153311057543380

fn make_move(from: char, to: char) -> String {
match (from, to) {
(from, to) if from == to => "",

('A', '>') => "v",
('A', '<') => "v<<",
('A', '^') => "<",
('A', 'v') => "v<",

('<', '>') => ">>",
('<', 'v') => ">",
('<', '^') => ">^",
('<', 'A') => ">>^",

('^', 'A') => ">",
('^', '>') => ">v",
('^', 'v') => "v",
('^', '<') => "v<",

('>', 'A') => "^",
('>', '^') => "<^",
('>', 'v') => "<",
('>', '<') => "<<",

('v', 'A') => ">^",
('v', '>') => ">",
('v', '^') => "^",
('v', '<') => "<",

_ => unreachable!("{from} -> {to}"),
}
.to_string()
+ "A"
}

fn expand_move(m: &str) -> Vec<String> {
("A".to_string() + m).chars().tuple_windows().map(|(from, to)| make_move(from, to)).collect()
}

fn expand_path(path: BTreeMap<String, usize>) -> BTreeMap<String, usize> {
path.into_iter()
.flat_map(|(m, count)| expand_move(&m).into_iter().map(move |m| (m, count)))
.fold(BTreeMap::new(), |mut acc, (m, count)| {
*acc.entry(m).or_insert(0) += count;
acc
})
}

#[cfg(test)]
mod tests {
use std::collections::BTreeSet;

use super::*;
#[test]
fn test_examples() {
dbg!(shortest_sequence("0", 2));
dbg!(shortest_sequence("2", 2));
dbg!(shortest_sequence("3", 2));
dbg!(shortest_sequence("6", 2));
dbg!(shortest_sequence("8", 2));

dbg!(make_move('A', '<'));
dbg!(expand_path([(make_move('A', '<'), 1)].into_iter().collect()));

dbg!(make_move('<', 'A'));
dbg!(expand_path([(make_move('<', 'A'), 1)].into_iter().collect()));

//dbg!(expand_path(BTreeMap::from([("<A".to_string(), 2)])));
//
//dbg!(expand_path(BTreeMap::from([("<A".to_string(), 1), (">A".to_string(), 1)])));
//
//dbg!(expand_path(BTreeMap::from([("<A".to_string(), 2), (">A".to_string(), 1)])));

dbg!(expand_path(BTreeMap::from([("<A".to_string(), 1)])));
dbg!(expand_path(expand_path(BTreeMap::from([("<A".to_string(), 1)]))));
dbg!(expand_path(expand_path(expand_path(BTreeMap::from([("<A".to_string(), 1)])))));

//return;

//assert_eq!(shortest_sequence("0", 0), 1);
//assert_eq!(shortest_sequence("0", 1), 6);
//// <A
//assert_eq!(shortest_sequence("0", 0), 6);
//assert_eq!(shortest_sequence("0", 1), 8);
//
//return;

//assert_eq!(shortest_sequence("029A", 0), 8);
//assert_eq!(shortest_sequence("029A", 1), 6 + 2 + 2 + 2 + 2 + 4 + 4 + 4);
assert_eq!(shortest_sequence("029A", 1), 68);
// assert_eq!(shortest_sequence("980A".to_string(), 0, 3, &mut HashMap::new()), 60);
// assert_eq!(shortest_sequence("179A".to_string(), 0, 3, &mut HashMap::new()), 68);
// assert_eq!(shortest_sequence("456A".to_string(), 0, 3, &mut HashMap::new()), 64);
// assert_eq!(shortest_sequence("379A".to_string(), 0, 3, &mut HashMap::new()), 64);
//
assert_example!(
"
029A
980A
179A
456A
379A",
126384
);
}
}
Loading

0 comments on commit e5aa40c

Please sign in to comment.