generated from janezicmatej/aoc-template
Compare commits
5 Commits
day20
...
000004106a
| Author | SHA1 | Date | |
|---|---|---|---|
|
000004106a
|
|||
|
000004006d
|
|||
|
0000039019
|
|||
|
0000038000
|
|||
|
000003704a
|
5
data/examples/20-1.txt
Normal file
5
data/examples/20-1.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
broadcaster -> a, b, c
|
||||||
|
%a -> b
|
||||||
|
%b -> c
|
||||||
|
%c -> inv
|
||||||
|
&inv -> a
|
||||||
5
data/examples/20-2.txt
Normal file
5
data/examples/20-2.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
broadcaster -> a
|
||||||
|
%a -> inv, con
|
||||||
|
&inv -> b
|
||||||
|
%b -> con
|
||||||
|
&con -> output
|
||||||
11
data/examples/21.txt
Normal file
11
data/examples/21.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
...........
|
||||||
|
.....###.#.
|
||||||
|
.###.##..#.
|
||||||
|
..#.#...#..
|
||||||
|
....#.#....
|
||||||
|
.##..S####.
|
||||||
|
.##..#...#.
|
||||||
|
.......##..
|
||||||
|
.##.#.####.
|
||||||
|
.##..##.##.
|
||||||
|
...........
|
||||||
7
data/examples/22.txt
Normal file
7
data/examples/22.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
1,0,1~1,2,1
|
||||||
|
0,0,2~2,0,2
|
||||||
|
0,2,3~2,2,3
|
||||||
|
0,0,4~0,2,4
|
||||||
|
2,0,5~2,2,5
|
||||||
|
0,1,6~2,1,6
|
||||||
|
1,1,9~1,1,8
|
||||||
23
data/examples/23.txt
Normal file
23
data/examples/23.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#.#####################
|
||||||
|
#.......#########...###
|
||||||
|
#######.#########.#.###
|
||||||
|
###.....#.>.>.###.#.###
|
||||||
|
###v#####.#v#.###.#.###
|
||||||
|
###.>...#.#.#.....#...#
|
||||||
|
###v###.#.#.#########.#
|
||||||
|
###...#.#.#.......#...#
|
||||||
|
#####.#.#.#######.#.###
|
||||||
|
#.....#.#.#.......#...#
|
||||||
|
#.#####.#.#.#########v#
|
||||||
|
#.#...#...#...###...>.#
|
||||||
|
#.#.#v#######v###.###v#
|
||||||
|
#...#.>.#...>.>.#.###.#
|
||||||
|
#####v#.#.###v#.#.###.#
|
||||||
|
#.....#...#...#.#.#...#
|
||||||
|
#.#########.###.#.#.###
|
||||||
|
#...###...#...#...#.###
|
||||||
|
###.###.#.###v#####v###
|
||||||
|
#...#...#.#.>.>.#.>.###
|
||||||
|
#.###.###.#.###.#.#v###
|
||||||
|
#.....###...###...#...#
|
||||||
|
#####################.#
|
||||||
206
src/bin/20.rs
Normal file
206
src/bin/20.rs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use aoc::lcm;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
enum Module {
|
||||||
|
Broadcaster,
|
||||||
|
FlipFlop(bool),
|
||||||
|
Conjuction,
|
||||||
|
Output,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseModuleError;
|
||||||
|
|
||||||
|
impl FromStr for Module {
|
||||||
|
type Err = ParseModuleError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"%" => Module::FlipFlop(false),
|
||||||
|
"&" => Module::Conjuction,
|
||||||
|
"b" => Module::Broadcaster,
|
||||||
|
_ => return Err(ParseModuleError),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Node {
|
||||||
|
index: usize,
|
||||||
|
module: Module,
|
||||||
|
inputs: Vec<usize>,
|
||||||
|
outputs: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_input(input: &str) -> Vec<Node> {
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
let mut mapper: HashMap<String, usize> = HashMap::new();
|
||||||
|
|
||||||
|
let output = Node {
|
||||||
|
index: 0,
|
||||||
|
module: Module::Output,
|
||||||
|
inputs: Vec::new(),
|
||||||
|
outputs: Vec::new(),
|
||||||
|
};
|
||||||
|
nodes.push(output);
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
let (from, _) = line.split_once(" -> ").unwrap();
|
||||||
|
let (module, name) = from.split_at(1);
|
||||||
|
let module: Module = module.parse().unwrap();
|
||||||
|
|
||||||
|
*mapper.entry(name.to_string()).or_default() = nodes.len();
|
||||||
|
|
||||||
|
let l = Node {
|
||||||
|
module,
|
||||||
|
index: nodes.len(),
|
||||||
|
inputs: Vec::new(),
|
||||||
|
outputs: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.push(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
let (from, to) = line.split_once(" -> ").unwrap();
|
||||||
|
let (_, name) = from.split_at(1);
|
||||||
|
|
||||||
|
let index = mapper[name];
|
||||||
|
for destination in to.split(", ") {
|
||||||
|
let to_index = *mapper.get(destination).unwrap_or(&0);
|
||||||
|
nodes[index].outputs.push(to_index);
|
||||||
|
nodes[to_index].inputs.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle(input: &str, button_presses: Option<usize>) -> usize {
|
||||||
|
let mut nodes = parse_input(input);
|
||||||
|
|
||||||
|
let mut graph = vec![vec![false; nodes.len()]; nodes.len()];
|
||||||
|
|
||||||
|
let mut highs = 0;
|
||||||
|
let mut lows = 0;
|
||||||
|
|
||||||
|
let broadcaster = nodes
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.module == Module::Broadcaster)
|
||||||
|
.unwrap()
|
||||||
|
.index;
|
||||||
|
|
||||||
|
let mut hm = HashMap::new();
|
||||||
|
let critical_node = nodes[0].inputs.first().copied();
|
||||||
|
let critical_inputs = critical_node.map(|x| nodes[x].inputs.len());
|
||||||
|
|
||||||
|
for i in 1..=button_presses.unwrap_or(usize::MAX) {
|
||||||
|
if let Some(c) = critical_inputs {
|
||||||
|
if button_presses.is_none() && hm.values().len() == c {
|
||||||
|
return hm.values().copied().reduce(lcm).unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stack = VecDeque::from([(broadcaster, false)]);
|
||||||
|
|
||||||
|
while let Some((index, signal)) = stack.pop_front() {
|
||||||
|
if signal {
|
||||||
|
highs += 1;
|
||||||
|
} else {
|
||||||
|
lows += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = &nodes[index];
|
||||||
|
let mut new_module = None;
|
||||||
|
|
||||||
|
match node.module {
|
||||||
|
Module::Output => (),
|
||||||
|
Module::Broadcaster => {
|
||||||
|
for dest_index in node.outputs.iter().copied() {
|
||||||
|
stack.push_back((dest_index, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Module::FlipFlop(high) => {
|
||||||
|
if !signal {
|
||||||
|
let signal = !high;
|
||||||
|
|
||||||
|
for dest_index in nodes[index].outputs.iter().copied() {
|
||||||
|
graph[index][dest_index] = signal;
|
||||||
|
stack.push_back((dest_index, signal));
|
||||||
|
}
|
||||||
|
|
||||||
|
new_module = Some(Module::FlipFlop(signal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Module::Conjuction => {
|
||||||
|
let mut all = true;
|
||||||
|
|
||||||
|
for in_index in nodes[index].inputs.iter().copied() {
|
||||||
|
all &= graph[in_index][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
for dest_index in nodes[index].outputs.iter().copied() {
|
||||||
|
graph[index][dest_index] = !all;
|
||||||
|
stack.push_back((dest_index, !all));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(c) = critical_node {
|
||||||
|
if index == c {
|
||||||
|
for in_index in nodes[index].inputs.iter().copied() {
|
||||||
|
if graph[in_index][index] {
|
||||||
|
hm.entry(in_index).or_insert(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = &mut nodes[index];
|
||||||
|
if let Some(module) = new_module {
|
||||||
|
node.module = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
highs * lows
|
||||||
|
}
|
||||||
|
pub fn part_one(input: &str) -> Option<usize> {
|
||||||
|
Some(cycle(input, Some(1000)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<usize> {
|
||||||
|
Some(cycle(input, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(20);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one_first() {
|
||||||
|
assert_eq!(
|
||||||
|
part_one(&aoc::template::read_file_part("examples", 20, 1)),
|
||||||
|
Some(32000000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_one_second() {
|
||||||
|
assert_eq!(
|
||||||
|
part_one(&aoc::template::read_file_part("examples", 20, 2)),
|
||||||
|
Some(11687500)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(
|
||||||
|
part_two(&aoc::template::read_file_part("examples", 20, 2)),
|
||||||
|
Some(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/bin/21.rs
Normal file
106
src/bin/21.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use std::{collections::HashSet, usize};
|
||||||
|
|
||||||
|
fn parse_input(input: &str) -> (Vec<Vec<u8>>, (isize, isize)) {
|
||||||
|
let mut grid: Vec<_> = input.lines().map(|x| x.as_bytes().to_vec()).collect();
|
||||||
|
|
||||||
|
let start = grid
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(y, item)| item.iter().enumerate().map(move |(x, item)| (x, y, *item)))
|
||||||
|
.find_map(|(x, y, s)| if s == b'S' { Some((y, x)) } else { None })
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
grid[start.0][start.1] = b'.';
|
||||||
|
|
||||||
|
(grid, (start.0 as isize, start.1 as isize))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_return_at(
|
||||||
|
grid: &[Vec<u8>],
|
||||||
|
start: (isize, isize),
|
||||||
|
mut returns: Vec<usize>,
|
||||||
|
can_cycle: bool,
|
||||||
|
) -> Vec<usize> {
|
||||||
|
returns.sort_by(|a, b| b.cmp(a));
|
||||||
|
|
||||||
|
let h = grid.len() as isize;
|
||||||
|
let w = grid[0].len() as isize;
|
||||||
|
|
||||||
|
let invalid_indexing = |y, x| y < 0 || y >= h || x < 0 || x >= w;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
let length = returns[0];
|
||||||
|
let mut next = returns.pop().unwrap();
|
||||||
|
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
visited.insert(start);
|
||||||
|
|
||||||
|
for i in 1..=length {
|
||||||
|
let mut new_visited = HashSet::new();
|
||||||
|
|
||||||
|
for (y, x) in visited.iter() {
|
||||||
|
for (dy, dx) in [(1, 0), (0, 1), (-1, 0), (0, -1)] {
|
||||||
|
let (ny, nx) = (y + dy, x + dx);
|
||||||
|
|
||||||
|
if !can_cycle && invalid_indexing(ny, nx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (cy, cx) = (ny.rem_euclid(h) as usize, nx.rem_euclid(w) as usize);
|
||||||
|
|
||||||
|
if grid[cy][cx] == b'.' {
|
||||||
|
new_visited.insert((ny, nx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visited = new_visited;
|
||||||
|
|
||||||
|
if i == next {
|
||||||
|
results.push(visited.len());
|
||||||
|
if !returns.is_empty() {
|
||||||
|
next = returns.pop().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_one(input: &str) -> Option<usize> {
|
||||||
|
let (grid, start) = parse_input(input);
|
||||||
|
let result = walk_return_at(&grid, start, vec![64], false);
|
||||||
|
|
||||||
|
Some(result[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<usize> {
|
||||||
|
let (grid, start) = parse_input(input);
|
||||||
|
|
||||||
|
let h = grid.len();
|
||||||
|
let s = start.0 as usize;
|
||||||
|
|
||||||
|
let result = walk_return_at(&grid, start, vec![s, s + h, s + 2 * h], true);
|
||||||
|
|
||||||
|
let a = (result[2] - 2 * result[1] + result[0]) / 2;
|
||||||
|
let b = (result[1] - result[0]) - a;
|
||||||
|
let c = result[0];
|
||||||
|
|
||||||
|
let x = 26501365 / h;
|
||||||
|
|
||||||
|
Some(a * x * x + b * x + c)
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(21);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(
|
||||||
|
part_one(&aoc::template::read_file("examples", 21)),
|
||||||
|
Some(42)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
143
src/bin/22.rs
Normal file
143
src/bin/22.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
struct Brick {
|
||||||
|
from: (usize, usize, usize),
|
||||||
|
to: (usize, usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseBrickError;
|
||||||
|
|
||||||
|
impl FromStr for Brick {
|
||||||
|
type Err = ParseBrickError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (first, second) = s.split_once('~').ok_or(ParseBrickError)?;
|
||||||
|
|
||||||
|
let sorted: Vec<_> = first
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|y| y.parse().ok())
|
||||||
|
.zip(second.split(',').filter_map(|y| y.parse().ok()))
|
||||||
|
.map(|(x, y)| if x < y { (x, y) } else { (y, x) })
|
||||||
|
.collect();
|
||||||
|
let from = (sorted[0].0, sorted[1].0, sorted[2].0);
|
||||||
|
let to = (sorted[0].1, sorted[1].1, sorted[2].1);
|
||||||
|
|
||||||
|
Ok(Self { from, to })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Brick {
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.to.2 - self.from.2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn points(&self) -> impl Iterator<Item = (usize, usize, usize)> + '_ {
|
||||||
|
(self.from.0..=self.to.0).flat_map(move |x| {
|
||||||
|
(self.from.1..=self.to.1)
|
||||||
|
.flat_map(move |y| (self.from.2..=self.to.2).map(move |z| (x, y, z)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Brick {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Brick {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.from
|
||||||
|
.2
|
||||||
|
.cmp(&other.from.2)
|
||||||
|
.then((self.from.0, self.from.1).cmp(&(other.from.0, other.from.1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Tower {
|
||||||
|
bricks: Vec<Brick>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tower {
|
||||||
|
fn new(mut bricks: Vec<Brick>) -> Self {
|
||||||
|
bricks.sort();
|
||||||
|
let mut ret = Self { bricks };
|
||||||
|
ret.compress(None);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress(&mut self, skip: Option<usize>) -> usize {
|
||||||
|
let mut heights: HashMap<(usize, usize), usize> = HashMap::new();
|
||||||
|
let mut moved = 0;
|
||||||
|
|
||||||
|
for brick in self
|
||||||
|
.bricks
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| *i != skip.unwrap_or(usize::MAX))
|
||||||
|
.map(|(_, x)| x)
|
||||||
|
{
|
||||||
|
let height = brick.height();
|
||||||
|
let new_height = brick
|
||||||
|
.points()
|
||||||
|
.map(|(x, y, _)| *heights.entry((x, y)).or_default())
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for (x, y, _) in brick.points() {
|
||||||
|
*heights.get_mut(&(x, y)).unwrap() = new_height + height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_height + 1 == brick.from.2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if skip.is_none() {
|
||||||
|
brick.from.2 = new_height + 1;
|
||||||
|
brick.to.2 = new_height + height;
|
||||||
|
}
|
||||||
|
|
||||||
|
moved += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
moved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_one(input: &str) -> Option<usize> {
|
||||||
|
let mut tower = Tower::new(input.lines().filter_map(|x| x.parse().ok()).collect());
|
||||||
|
|
||||||
|
Some(
|
||||||
|
(0..tower.bricks.len())
|
||||||
|
.map(|i| tower.compress(Some(i)))
|
||||||
|
.filter(|m| *m == 0)
|
||||||
|
.count(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<usize> {
|
||||||
|
let mut tower = Tower::new(input.lines().filter_map(|x| x.parse().ok()).collect());
|
||||||
|
|
||||||
|
Some(
|
||||||
|
(0..tower.bricks.len())
|
||||||
|
.map(|i| tower.compress(Some(i)))
|
||||||
|
.sum(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(22);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(part_one(&aoc::template::read_file("examples", 22)), Some(5));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(part_two(&aoc::template::read_file("examples", 22)), Some(7));
|
||||||
|
}
|
||||||
|
}
|
||||||
161
src/bin/23.rs
Normal file
161
src/bin/23.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
iter::once,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DIRS: [(isize, isize); 4] = [(1, 0), (0, 1), (-1, 0), (0, -1)];
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Node {
|
||||||
|
neighbours: Vec<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_graph(input: &str, two_way: bool) -> (Vec<Node>, usize) {
|
||||||
|
let mut grid: Vec<Vec<_>> = input.lines().map(|x| x.as_bytes().to_vec()).collect();
|
||||||
|
|
||||||
|
let l = grid[0].len();
|
||||||
|
|
||||||
|
let border: Vec<_> = once(b'#').cycle().take(l).collect();
|
||||||
|
grid.insert(0, border.clone());
|
||||||
|
grid.push(border);
|
||||||
|
|
||||||
|
let start = (1, 1);
|
||||||
|
let end = (grid.len() - 2, grid[0].len() - 2);
|
||||||
|
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
|
||||||
|
let mut mapper = HashMap::new();
|
||||||
|
mapper.insert(start, 0);
|
||||||
|
|
||||||
|
let mut visited: HashSet<(usize, usize)> = HashSet::new();
|
||||||
|
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
// (from_node, at, len)
|
||||||
|
stack.push((start, start, 0));
|
||||||
|
|
||||||
|
let forced = |x| match x {
|
||||||
|
b'>' => (0, 1),
|
||||||
|
b'v' => (1, 0),
|
||||||
|
_ => (0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some((from, (y, x), len)) = stack.pop() {
|
||||||
|
let is_node = DIRS
|
||||||
|
.iter()
|
||||||
|
.filter(|(dy, dx)| {
|
||||||
|
grid[((y as isize) + dy) as usize][((x as isize) + dx) as usize] != b'#'
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
> 2
|
||||||
|
|| (y, x) == start
|
||||||
|
|| (y, x) == end;
|
||||||
|
|
||||||
|
if is_node {
|
||||||
|
if !visited.contains(&(y, x)) {
|
||||||
|
mapper.insert((y, x), nodes.len());
|
||||||
|
nodes.push(Node::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
if from != (y, x) {
|
||||||
|
nodes[mapper[&from]].neighbours.push((mapper[&(y, x)], len));
|
||||||
|
if two_way {
|
||||||
|
nodes[mapper[&(y, x)]].neighbours.push((mapper[&from], len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !visited.insert((y, x)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ny, nx, _, _) in DIRS
|
||||||
|
.iter()
|
||||||
|
.map(|(dy, dx)| {
|
||||||
|
(
|
||||||
|
((y as isize) + dy) as usize,
|
||||||
|
((x as isize) + dx) as usize,
|
||||||
|
dy,
|
||||||
|
dx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(|(ny, nx, _, _)| grid[*ny][*nx] != b'#')
|
||||||
|
.filter(|(ny, nx, dy, dx)| {
|
||||||
|
let (fy, fx) = forced(grid[*ny][*nx]);
|
||||||
|
(fy + **dy, fx + **dx) != (0, 0)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let new_len = if is_node { 1 } else { len + 1 };
|
||||||
|
let new_from = if is_node { (y, x) } else { from };
|
||||||
|
stack.push((new_from, (ny, nx), new_len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(nodes, mapper[&end])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn longest_path(
|
||||||
|
nodes: &[Node],
|
||||||
|
mut visited: usize,
|
||||||
|
location: usize,
|
||||||
|
target: usize,
|
||||||
|
length: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
if location == target {
|
||||||
|
return Some(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// binary mask for visited since < 64 nodes in input
|
||||||
|
// nth bit tells if location n was visited already
|
||||||
|
visited |= 1 << location;
|
||||||
|
|
||||||
|
let mut max_len = 0;
|
||||||
|
|
||||||
|
for (n, l) in nodes[location]
|
||||||
|
.neighbours
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|(n, _)| (visited & 1 << n) == 0)
|
||||||
|
{
|
||||||
|
if let Some(new_len) = longest_path(nodes, visited, n, target, length + l) {
|
||||||
|
if max_len < new_len {
|
||||||
|
max_len = new_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if max_len > 0 {
|
||||||
|
Some(max_len)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_one(input: &str) -> Option<usize> {
|
||||||
|
let (nodes, target) = build_graph(input, false);
|
||||||
|
longest_path(&nodes, 0, 0, target, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<usize> {
|
||||||
|
let (nodes, target) = build_graph(input, true);
|
||||||
|
longest_path(&nodes, 0, 0, target, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(23);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(
|
||||||
|
part_one(&aoc::template::read_file("examples", 23)),
|
||||||
|
Some(94)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(
|
||||||
|
part_two(&aoc::template::read_file("examples", 23)),
|
||||||
|
Some(154)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user