Compare commits

...

2 Commits

Author SHA1 Message Date
Matej Janezic 00000270ae
whatever 2024-12-21 14:42:03 +01:00
Matej Janezic 00000260b8
fix: account for wrapping in grid.get and grid.get_mut 2024-12-21 12:47:20 +01:00
6 changed files with 442 additions and 2 deletions

15
data/examples/20.txt Normal file
View File

@ -0,0 +1,15 @@
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############

5
data/examples/21.txt Normal file
View File

@ -0,0 +1,5 @@
029A
980A
179A
456A
379A

148
src/bin/20.rs Normal file
View File

@ -0,0 +1,148 @@
use std::collections::VecDeque;
use aoc::grid_vec::{Direction, Grid, Point};
use hashbrown::HashMap;
fn parse_input(input: &str) -> (Grid, Point, Point) {
let mut grid: Grid = input.parse().unwrap();
let start = grid.find(b'S').unwrap();
let end = grid.find(b'E').unwrap();
grid[start] = b'.';
grid[end] = b'.';
(grid, start, end)
}
fn bfs(
grid: &Grid,
start: Point,
limit: Option<usize>,
cheat: bool,
visited: &mut HashMap<Point, usize>,
queue: &mut VecDeque<(Point, usize)>,
) {
visited.clear();
queue.clear();
queue.push_back((start, 0));
while let Some((p, l)) = queue.pop_front() {
if visited.contains_key(&p) {
continue;
}
visited.insert(p, l);
for d in Direction::CROSS {
let n = p + d;
let nv = grid.get(&n);
let uwnv = *nv.unwrap_or(&b'#');
let valid_neigh = (cheat && nv.is_some()) || uwnv == b'.';
let valid_length = l < limit.unwrap_or(usize::MAX);
let unvisited = !visited.contains_key(&n);
if valid_neigh && valid_length && unvisited {
queue.push_back((n, l + 1));
}
}
}
}
fn get_cheat_neighbours_and_benchmark(
grid: &Grid,
start: Point,
cheat_limit: usize,
) -> HashMap<Point, Vec<(Point, usize)>> {
let mut map = HashMap::<Point, Vec<(Point, usize)>>::new();
let mut reuse_queue = VecDeque::new();
let mut reuse_visited = HashMap::new();
let mut valid_points = HashMap::new();
bfs(
grid,
start,
None,
false,
&mut valid_points,
&mut reuse_queue,
);
for (&s, _) in valid_points.iter() {
bfs(
grid,
s,
Some(cheat_limit),
true,
&mut reuse_visited,
&mut reuse_queue,
);
for (&point, &length) in reuse_visited.iter().filter(|(&p, _)| grid[p] == b'.') {
map.entry(s).or_default().push((point, length));
}
}
map
}
fn cheated_bfs(
grid: &Grid,
start: Point,
end: Point,
cheat_limit: usize,
increase_limit: usize,
) -> usize {
let neighbours = get_cheat_neighbours_and_benchmark(grid, start, cheat_limit);
let mut reuse_queue = VecDeque::new();
let mut from_start = HashMap::new();
let mut from_end = HashMap::new();
bfs(grid, start, None, false, &mut from_start, &mut reuse_queue);
bfs(grid, end, None, false, &mut from_end, &mut reuse_queue);
let benchmark = from_start[&end];
let mut count = 0;
for node in from_start.keys() {
for (neigh, cheat_dist) in neighbours[node].iter() {
let start_dist = from_start[node];
let end_dist = from_end[neigh];
let dist = start_dist + cheat_dist + end_dist;
if dist < benchmark && dist.abs_diff(benchmark) >= increase_limit {
count += 1;
}
}
}
count
}
pub fn part_one(input: &str) -> Option<usize> {
let (grid, start, end) = parse_input(input);
cheated_bfs(&grid, start, end, 2, 100).into()
}
pub fn part_two(input: &str) -> Option<usize> {
let (grid, start, end) = parse_input(input);
cheated_bfs(&grid, start, end, 20, 100).into()
}
aoc::solution!(20);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 20)), None);
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 20)), None);
}
}

262
src/bin/21.rs Normal file
View File

@ -0,0 +1,262 @@
use std::iter::once;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
const NUMPAD: [(&str, &str); 110] = [
// 0 - everywhere
("01", "^<"),
("02", "^"),
("03", "^>"),
("04", "^^<"),
("05", "^^"),
("06", "^^>"),
("07", "^^^<"),
("08", "^^^"),
("09", "^^^>"),
("0A", ">"),
// 1 - everywhere
("10", ">v"),
("12", ">"),
("13", ">>"),
("14", "^"),
("15", "^>"),
("16", "^>>"),
("17", "^^"),
("18", "^^>"),
("19", "^^>>"),
("1A", ">>v"),
// 2 - everywhere
("20", "v"),
("21", "<"),
("23", ">"),
("24", "^<"),
("25", "^"),
("26", "^>"),
("27", "^^<"),
("28", "^^"),
("29", "^^>"),
("2A", ">v"),
// 3 - everywhere
("30", "v<"),
("31", "<<"),
("32", "<"),
("34", "^<<"),
("35", "^<"),
("36", "^"),
("37", "^^<<"),
("38", "^^<"),
("39", "^^"),
("3A", "v"),
// 4 - everywhere
("40", ">vv"),
("41", "v"),
("42", "v>"),
("43", "v>>"),
("45", ">"),
("46", ">>"),
("47", "^"),
("48", "^>"),
("49", "^>>"),
("4A", ">>vv"),
// 5 - everywhere
("50", "vv"),
("51", "v<"),
("52", "v"),
("53", "v>"),
("54", "<"),
("56", ">"),
("57", "^<"),
("58", "^"),
("59", "^>"),
("5A", ">vv"),
// 6 - everywhere
("60", "vv<"),
("61", "v<<"),
("62", "v<"),
("63", "v"),
("64", "<<"),
("65", "<"),
("67", "^<<"),
("68", "^<"),
("69", "^"),
("6A", "vv"),
// 7 - everywhere
("70", ">vvv"),
("71", "vv"),
("72", "vv>"),
("73", "vv>>"),
("74", "v"),
("75", "v>"),
("76", "v>>"),
("78", ">"),
("79", ">>"),
("7A", ">>vvv"),
// 8 - everywhere
("80", "vvv"),
("81", "vv<"),
("82", "vv"),
("83", "vv>"),
("84", "v<"),
("85", "v"),
("86", "v>"),
("87", "<"),
("89", ">"),
("8A", ">vvv"),
// 9 - everywhere
("90", "<vvv"),
("91", "vv<<"),
("92", "vv<"),
("93", "vv"),
("94", "v<<"),
("95", "v<"),
("96", "v"),
("97", "<<"),
("98", "<"),
("9A", "vvv"),
// A - everywhere
("A0", "<"),
("A1", "^<<"),
("A2", "^<"),
("A3", "^"),
("A4", "^^<<"),
("A5", "^^<"),
("A6", "^^"),
("A7", "^^^<<"),
("A8", "^^^<"),
("A9", "^^^"),
];
const KEYPAD: [(&str, &str); 20] = [
// < - everywhere
("<v", ">"),
("<>", ">>"),
("<^", ">^"),
("<A", ">>^"),
// v - everywhere
("v<", "<"),
("v>", ">"),
("v^", "^"),
("vA", ">^"),
// > - everywhere
("><", "<<"),
(">v", "<"),
(">^", "^<"),
(">A", "^"),
// ^ - everywhere
("^<", "v<"),
("^v", "v"),
("^>", "v>"),
("^A", ">"),
// A - everywhere
("A<", "v<<"),
("Av", "v<"),
("A>", "v"),
("A^", "<"),
];
fn build_move_options(s: &str) -> Vec<String> {
let mut set = HashSet::new();
for c in s.chars() {
set.insert(c);
}
let mut v = vec![s.to_string()];
if set.len() == 2 {
v.push(s.chars().rev().collect())
}
v
}
pub fn part_one(input: &str) -> Option<usize> {
let mut keypad = HashMap::new();
for (k, v) in KEYPAD.iter() {
let key = k.as_bytes();
keypad.insert((key[0], key[1]), build_move_options(v));
}
let mut numpad = HashMap::new();
for (k, v) in NUMPAD.iter() {
let key = k.as_bytes();
numpad.insert((key[0], key[1]), build_move_options(v));
}
let mut s1s = Vec::new();
let mut s2s = Vec::new();
let mut s3s = Vec::new();
let mut s4s = Vec::new();
let mut c = 0;
for line in input.lines() {
s1s.clear();
s2s.clear();
s3s.clear();
s4s.clear();
s1s.push(once(&b'A').chain(line.as_bytes()).copied().collect_vec());
for mapper in [&numpad, &keypad, &keypad] {
for s1 in s1s.iter() {
s3s.push(vec![b'A']);
for i in 1..s1.len() {
let k2 = s1[i - 1];
let k1 = s1[i];
if mapper.contains_key(&(k1, k2)) {
for path in mapper[&(k1, k2)].iter() {
for s3 in s3s.iter() {
let mut cs3 = s3.clone();
cs3.extend(path.as_bytes());
s4s.push(cs3);
}
}
}
s3s.clear();
s3s.append(&mut s4s);
for s3 in s3s.iter_mut() {
s3.push(b'A');
}
}
s2s.append(&mut s3s);
}
s1s.clear();
s1s.append(&mut s2s);
}
let min_len = s1s.iter().map(|v| v.len()).min().unwrap();
let n: usize = line.strip_suffix("A").unwrap().parse().unwrap();
println!("{min_len}, {n}");
c += n * min_len;
}
c.into()
}
pub fn part_two(input: &str) -> Option<usize> {
None
}
aoc::solution!(21);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 21)), None);
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 21)), None);
}
}

View File

@ -43,7 +43,7 @@ impl Display for Grid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (idx, cell) in self.grid.iter().enumerate() {
write!(f, "{}", *cell as char)?;
if idx > 0 && idx % self.width == 0 {
if idx > 0 && (idx + 1) % self.width == 0 {
writeln!(f)?;
}
}
@ -77,10 +77,20 @@ impl Grid {
}
pub fn get(&self, p: &Point) -> Option<&u8> {
if p.i >= self.grid.len() / self.width || p.j >= self.width {
return None;
}
self.grid.get(p.i * self.width + p.j)
}
pub fn get_mut(&mut self, p: &Point) -> Option<&mut u8> {
if p.i >= self.grid.len() / self.width || p.j >= self.width {
return None;
}
self.grid.get_mut(p.i * self.width + p.j)
}
pub fn size(&self) -> (usize, usize) {
(self.grid.len() / self.width, self.width)
}
}

View File

@ -89,7 +89,7 @@ impl Point {
#[macro_export]
macro_rules! pnt {
($i:literal, $j:literal) => {
($i:expr, $j:expr) => {
Point { i: $i, j: $j }
};
}