Compare commits
21 Commits
22d9f7c42a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5642d65bff | |||
| 745d4a8029 | |||
| a7d32abb93 | |||
| 98fcdfeaef | |||
| 7abd632567 | |||
| 62c866abf9 | |||
| ae143d4c57 | |||
| 5e6870d092 | |||
| a0fa5256e9 | |||
| 49368e7985 | |||
| 95e4095f32 | |||
| 0279edb249 | |||
| 47cb2fefa5 | |||
| ec5b46d793 | |||
| 9b42768fdb | |||
| 93f574463b | |||
| 63fecbd235 | |||
| f825de9426 | |||
| ab870a881e | |||
| 813c9f0612 | |||
| ff8870eb3e |
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -22,6 +22,7 @@ dependencies = [
|
|||||||
"num",
|
"num",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
|
"trees",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -340,6 +341,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trees"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de5f738ceab88e2491a94ddc33c3feeadfa95fedc60363ef110845df12f3878"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|||||||
@ -13,6 +13,7 @@ env_logger = "^0.10"
|
|||||||
num = "^0.1"
|
num = "^0.1"
|
||||||
lazy_static = "^1.4"
|
lazy_static = "^1.4"
|
||||||
once_cell = "^1.16"
|
once_cell = "^1.16"
|
||||||
|
trees = "^0.4"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "d1p1"
|
name = "d1p1"
|
||||||
|
|||||||
@ -1,73 +1,19 @@
|
|||||||
use std::{
|
use aoc22::{
|
||||||
sync::{
|
day16,
|
||||||
atomic::{AtomicUsize, Ordering},
|
util::{self, BnBState},
|
||||||
Arc, Mutex,
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use aoc22::{day16, util};
|
const TOTAL_TIME: usize = 30;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let valves = Arc::new(day16::parse_valves(&util::parse_input()));
|
let valves = day16::parse_valves(&util::parse_input());
|
||||||
let dists = Arc::new(day16::calc_dists(&valves));
|
let paths = day16::calc_paths(&valves);
|
||||||
let state = day16::State::new(&valves);
|
let state = day16::State::new(&valves, TOTAL_TIME, 1);
|
||||||
|
|
||||||
let possible_states = Arc::new(Mutex::new(Vec::new()));
|
let state = util::maximize(&state, &(&valves, &paths));
|
||||||
possible_states.lock().unwrap().push(state);
|
|
||||||
|
|
||||||
let lower_bound = Arc::new(AtomicUsize::new(0));
|
|
||||||
|
|
||||||
let mut handles = Vec::new();
|
|
||||||
|
|
||||||
for _ in 0..16 {
|
|
||||||
let s = possible_states.clone();
|
|
||||||
let l = lower_bound.clone();
|
|
||||||
let v = valves.clone();
|
|
||||||
let d = dists.clone();
|
|
||||||
handles.push(thread::spawn(move || check_states(s, l, v, d)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for handle in handles {
|
|
||||||
handle.join().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Most pressure released is {}",
|
"Most pressure released is {}",
|
||||||
lower_bound.load(Ordering::Relaxed)
|
state.lower_bound(&(&valves, &paths))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_states(
|
|
||||||
possible_states: Arc<Mutex<Vec<day16::State>>>,
|
|
||||||
lower_bound: Arc<AtomicUsize>,
|
|
||||||
valves: Arc<Vec<day16::Valve>>,
|
|
||||||
dists: Arc<Vec<Vec<usize>>>,
|
|
||||||
) {
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
i += 1;
|
|
||||||
let state = { possible_states.lock().unwrap().pop() };
|
|
||||||
if state.is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let state = state.unwrap();
|
|
||||||
if state.finished() {
|
|
||||||
let score = state.lower_bound();
|
|
||||||
dbg!(score);
|
|
||||||
lower_bound.fetch_max(score, Ordering::Relaxed);
|
|
||||||
} else {
|
|
||||||
let x = day16::possible_actions(&state, &valves, &dists);
|
|
||||||
// let x = state.possible_actions(&valves, &dists);
|
|
||||||
// let state_upper = state.upper_bound(&valves, &dists);
|
|
||||||
for action in x {
|
|
||||||
let action_upper = action.upper_bound(&valves, &dists);
|
|
||||||
// assert!(action_upper <= state_upper);
|
|
||||||
if action_upper > lower_bound.load(Ordering::Relaxed) {
|
|
||||||
possible_states.lock().unwrap().push(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbg!(i);
|
|
||||||
}
|
|
||||||
|
|||||||
20
src/bin/d16p2.rs
Normal file
20
src/bin/d16p2.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use aoc22::{
|
||||||
|
day16,
|
||||||
|
util::{self, BnBState},
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOTAL_TIME: usize = 26;
|
||||||
|
const N_AGENTS: usize = 2;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let valves = day16::parse_valves(&util::parse_input());
|
||||||
|
let paths = day16::calc_paths(&valves);
|
||||||
|
let state = day16::State::new(&valves, TOTAL_TIME, N_AGENTS);
|
||||||
|
|
||||||
|
let state = util::maximize(&state, &(&valves, &paths));
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Most pressure released is {}",
|
||||||
|
state.lower_bound(&(&valves, &paths))
|
||||||
|
);
|
||||||
|
}
|
||||||
7
src/bin/d18p1.rs
Normal file
7
src/bin/d18p1.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use aoc22::{day18, util};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let droplet = day18::parse_droplet(&util::parse_input());
|
||||||
|
|
||||||
|
println!("Surface area: {}", day18::surface_area(&droplet));
|
||||||
|
}
|
||||||
10
src/bin/d18p2.rs
Normal file
10
src/bin/d18p2.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use aoc22::{day18, util};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let droplet = day18::parse_droplet(&util::parse_input());
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Outer surface area: {}",
|
||||||
|
day18::outer_surface_area(&droplet)
|
||||||
|
);
|
||||||
|
}
|
||||||
26
src/bin/d19p1.rs
Normal file
26
src/bin/d19p1.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use aoc22::{day19, util};
|
||||||
|
|
||||||
|
const MINUTES: usize = 24;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let blueprints = day19::parse_blueprints(&util::parse_input());
|
||||||
|
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
for (i, blueprint) in blueprints.iter().enumerate() {
|
||||||
|
let blueprint = blueprint.clone();
|
||||||
|
handles.push((
|
||||||
|
i,
|
||||||
|
thread::spawn(move || day19::max_geodes(MINUTES, &blueprint)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sum = 0;
|
||||||
|
for (i, handle) in handles {
|
||||||
|
let max = handle.join().unwrap();
|
||||||
|
sum += (i + 1) * max;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Sum of quality scores: {}", sum);
|
||||||
|
}
|
||||||
27
src/bin/d19p2.rs
Normal file
27
src/bin/d19p2.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use aoc22::{day19, util};
|
||||||
|
|
||||||
|
const MINUTES: usize = 32;
|
||||||
|
const MAX_BLUEPRINTS: usize = 3;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let blueprints = day19::parse_blueprints(&util::parse_input());
|
||||||
|
assert!(blueprints.len() > 0);
|
||||||
|
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
for blueprint in blueprints.iter().take(MAX_BLUEPRINTS) {
|
||||||
|
let blueprint = blueprint.clone();
|
||||||
|
handles.push(thread::spawn(move || {
|
||||||
|
day19::max_geodes(MINUTES, &blueprint)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prod = 1;
|
||||||
|
for handle in handles {
|
||||||
|
let max = handle.join().unwrap();
|
||||||
|
prod *= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Product of geodes: {}", prod);
|
||||||
|
}
|
||||||
9
src/bin/d20p1.rs
Normal file
9
src/bin/d20p1.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use aoc22::{day20, util};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let nodes = day20::parse_file(&util::parse_input(), 1);
|
||||||
|
|
||||||
|
day20::mix(&nodes);
|
||||||
|
|
||||||
|
println!("Sum of coordinates: {}", day20::calc_coordinates(&nodes));
|
||||||
|
}
|
||||||
16
src/bin/d20p2.rs
Normal file
16
src/bin/d20p2.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use aoc22::{day20, util};
|
||||||
|
|
||||||
|
const KEY: isize = 811589153;
|
||||||
|
const ROUNDS: usize = 10;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let nodes = day20::parse_file(&util::parse_input(), KEY);
|
||||||
|
// day20::print_nodes(&nodes);
|
||||||
|
|
||||||
|
for _ in 0..ROUNDS {
|
||||||
|
day20::mix(&nodes);
|
||||||
|
// day20::print_nodes(&nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Sum of coordinates: {}", day20::calc_coordinates(&nodes));
|
||||||
|
}
|
||||||
7
src/bin/d21p1.rs
Normal file
7
src/bin/d21p1.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use aoc22::{day21, util};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let monkeys = day21::parse_monkeys(&util::parse_input());
|
||||||
|
|
||||||
|
println!("root yells: {}", day21::yell(monkeys.root(), false));
|
||||||
|
}
|
||||||
9
src/bin/d21p2.rs
Normal file
9
src/bin/d21p2.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use aoc22::{day21, util};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let monkeys = day21::parse_monkeys(&util::parse_input());
|
||||||
|
|
||||||
|
let human = day21::find(&monkeys, "humn").unwrap();
|
||||||
|
|
||||||
|
println!("Human should yell: {}", day21::find_solution(human));
|
||||||
|
}
|
||||||
21
src/bin/d22p1.rs
Normal file
21
src/bin/d22p1.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use aoc22::{
|
||||||
|
day22::{self, Navigable},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let (grid, instructions) = day22::parse_map_and_path(&util::parse_input());
|
||||||
|
|
||||||
|
// dbg!(&grid);
|
||||||
|
// dbg!(&instructions);
|
||||||
|
|
||||||
|
let mut pose = grid.initial_pose();
|
||||||
|
// dbg!(&pose);
|
||||||
|
for inst in &instructions {
|
||||||
|
pose = grid.exec_instruction(&pose, inst).0;
|
||||||
|
// dbg!(&pose);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pass = 1000 * (pose.pos.0 + 1) + 4 * (pose.pos.1 + 1) + (pose.orientation as usize);
|
||||||
|
println!("Passowrd: {}", pass);
|
||||||
|
}
|
||||||
40
src/bin/d22p2.rs
Normal file
40
src/bin/d22p2.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use aoc22::{
|
||||||
|
day22::{self, CubeSide, Navigable},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let (grid, instructions) = day22::parse_map_and_path(&util::parse_input());
|
||||||
|
|
||||||
|
let mut pattern = HashMap::new();
|
||||||
|
pattern.insert(CubeSide::Top, (0, 1));
|
||||||
|
pattern.insert(CubeSide::Fore, (1, 1));
|
||||||
|
pattern.insert(CubeSide::Left, (2, 0));
|
||||||
|
pattern.insert(CubeSide::Back, (3, 0));
|
||||||
|
pattern.insert(CubeSide::Bottom, (2, 1));
|
||||||
|
pattern.insert(CubeSide::Right, (0, 2));
|
||||||
|
|
||||||
|
// Pattern for example input
|
||||||
|
// WARNING: CubeGrid::wrap_around is hardcoded for the pattern above
|
||||||
|
// pattern.insert(CubeSide::Top, (0, 2));
|
||||||
|
// pattern.insert(CubeSide::Fore, (1, 2));
|
||||||
|
// pattern.insert(CubeSide::Left, (1, 1));
|
||||||
|
// pattern.insert(CubeSide::Back, (1, 0));
|
||||||
|
// pattern.insert(CubeSide::Bottom, (2, 2));
|
||||||
|
// pattern.insert(CubeSide::Right, (2, 3));
|
||||||
|
|
||||||
|
let grid = day22::CubeGrid::from(&grid, &pattern);
|
||||||
|
let mut pose = grid.initial_pose();
|
||||||
|
for inst in &instructions {
|
||||||
|
pose = grid.exec_instruction(&pose, inst).0;
|
||||||
|
}
|
||||||
|
let pos = pose.1.pos;
|
||||||
|
let row_add = pattern[&pose.0].0 * grid.side_height;
|
||||||
|
let col_add = pattern[&pose.0].1 * grid.side_width;
|
||||||
|
|
||||||
|
let pass =
|
||||||
|
1000 * (row_add + pos.0 + 1) + 4 * (col_add + pos.1 + 1) + (pose.1.orientation as usize);
|
||||||
|
println!("Passowrd: {}", pass);
|
||||||
|
}
|
||||||
19
src/bin/d23p1.rs
Normal file
19
src/bin/d23p1.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use aoc22::{
|
||||||
|
day23::{self, empty_ground},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
const N_ROUNDS: usize = 10;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let mut elves = day23::parse_map(&util::parse_input());
|
||||||
|
|
||||||
|
for i in 0..N_ROUNDS {
|
||||||
|
let elves_too_close = day23::do_round(&mut elves, i);
|
||||||
|
if !elves_too_close {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Elves cover {} empty tiles", empty_ground(&elves));
|
||||||
|
}
|
||||||
20
src/bin/d23p2.rs
Normal file
20
src/bin/d23p2.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use aoc22::{
|
||||||
|
day23::{self},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let mut elves = day23::parse_map(&util::parse_input());
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let elves_too_close = day23::do_round(&mut elves, i);
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
if !elves_too_close {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("First idle round: {}", i);
|
||||||
|
}
|
||||||
13
src/bin/d24p1.rs
Normal file
13
src/bin/d24p1.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use aoc22::{
|
||||||
|
day24::{self},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let (map, start, target) = day24::parse_map(&util::parse_input());
|
||||||
|
|
||||||
|
day24::print_map(&map, &start);
|
||||||
|
|
||||||
|
let (rounds, _) = day24::find_path(&map, &start, &target);
|
||||||
|
println!("Goal is reachable in {} min", rounds);
|
||||||
|
}
|
||||||
19
src/bin/d24p2.rs
Normal file
19
src/bin/d24p2.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use aoc22::{
|
||||||
|
day24::{self},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let (map, start, target) = day24::parse_map(&util::parse_input());
|
||||||
|
|
||||||
|
day24::print_map(&map, &start);
|
||||||
|
|
||||||
|
let (rounds_1, map_1) = day24::find_path(&map, &start, &target);
|
||||||
|
println!("Goal is reachable in {} min", rounds_1);
|
||||||
|
let (rounds_2, map_2) = day24::find_path(&map_1, &target, &start);
|
||||||
|
println!("Start is reachable in {} min", rounds_2);
|
||||||
|
let (rounds_3, _) = day24::find_path(&map_2, &start, &target);
|
||||||
|
println!("Goal is reachable in {} min", rounds_2);
|
||||||
|
|
||||||
|
println!("Total time taken is {} min", rounds_1 + rounds_2 + rounds_3);
|
||||||
|
}
|
||||||
8
src/bin/d25p1.rs
Normal file
8
src/bin/d25p1.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use aoc22::{day25, util};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let numbers = day25::parse_snafus(&util::parse_input());
|
||||||
|
let sum: i64 = numbers.iter().sum();
|
||||||
|
println!("Sum of numbers: {}", sum);
|
||||||
|
println!("Sum of numbers, SNAFU'd: {}", day25::num_to_snafu(sum));
|
||||||
|
}
|
||||||
325
src/day16.rs
325
src/day16.rs
@ -1,14 +1,12 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
sync::Mutex,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
const TOTAL_TIME: usize = 30;
|
use crate::util;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Valve {
|
pub struct Valve {
|
||||||
@ -54,21 +52,40 @@ pub fn parse_valves(input: &String) -> Vec<Valve> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calc_dists(valves: &Vec<Valve>) -> Vec<Vec<usize>> {
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Path {
|
||||||
|
pub dist: usize,
|
||||||
|
pub path: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calc_paths(valves: &Vec<Valve>) -> Vec<Vec<Path>> {
|
||||||
let n = valves.len();
|
let n = valves.len();
|
||||||
let mut result = vec![vec![usize::MAX; n]; n];
|
|
||||||
|
// result[from][to] = (dist, path)
|
||||||
|
let mut result = vec![
|
||||||
|
vec![
|
||||||
|
Path {
|
||||||
|
dist: usize::MAX,
|
||||||
|
path: Vec::new()
|
||||||
|
};
|
||||||
|
n
|
||||||
|
];
|
||||||
|
n
|
||||||
|
];
|
||||||
|
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
result[i][i] = 0;
|
result[i][i].dist = 0;
|
||||||
let mut next = VecDeque::new();
|
let mut next = VecDeque::new();
|
||||||
next.push_back(i);
|
next.push_back(i);
|
||||||
while let Some(j) = next.pop_front() {
|
while let Some(j) = next.pop_front() {
|
||||||
let d_next = result[i][j] + 1;
|
let d_next = result[i][j].dist + 1;
|
||||||
for j in &valves[j].tunnels {
|
for tunnel in &valves[j].tunnels {
|
||||||
let d_tunnel = result[i].get_mut(*j).unwrap();
|
let d_tunnel = &result[i][*tunnel];
|
||||||
if d_next < *d_tunnel {
|
if d_next < d_tunnel.dist {
|
||||||
*d_tunnel = d_next;
|
let mut path = result[i][j].path.clone();
|
||||||
next.push_back(*j);
|
path.push(*tunnel);
|
||||||
|
result[i][*tunnel] = Path { dist: d_next, path };
|
||||||
|
next.push_back(*tunnel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,19 +94,28 @@ pub fn calc_dists(valves: &Vec<Valve>) -> Vec<Vec<usize>> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq)]
|
||||||
|
pub enum Goal {
|
||||||
|
Idle,
|
||||||
|
NothingReachable,
|
||||||
|
Opening(usize),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub opened: u64,
|
pub opened: u64,
|
||||||
pub location: usize,
|
pub locations: Vec<usize>,
|
||||||
|
pub goals: Vec<Goal>,
|
||||||
pub flow_rate: usize,
|
pub flow_rate: usize,
|
||||||
pub time_remaining: usize,
|
pub time_remaining: usize,
|
||||||
pub pressure_released: usize,
|
pub pressure_released: usize,
|
||||||
|
pub history: Vec<Vec<usize>>,
|
||||||
|
|
||||||
interesting: HashSet<usize>,
|
interesting: HashSet<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new(valves: &Vec<Valve>) -> State {
|
pub fn new(valves: &Vec<Valve>, initial_time: usize, agents: usize) -> State {
|
||||||
let interesting = valves
|
let interesting = valves
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -97,78 +123,244 @@ impl State {
|
|||||||
.collect();
|
.collect();
|
||||||
State {
|
State {
|
||||||
opened: 0,
|
opened: 0,
|
||||||
location: 0, // "AA" has to be the first valve alphabetically
|
locations: vec![0; agents],
|
||||||
|
goals: vec![Goal::Idle; agents],
|
||||||
flow_rate: 0,
|
flow_rate: 0,
|
||||||
time_remaining: TOTAL_TIME,
|
time_remaining: initial_time,
|
||||||
pressure_released: 0,
|
pressure_released: 0,
|
||||||
interesting,
|
interesting,
|
||||||
|
history: vec![vec![0]; agents],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finished(&self) -> bool {
|
fn find_goals(&self, (_, paths): &ExtraArgs) -> Vec<State> {
|
||||||
self.time_remaining == 0 || self.interesting.is_empty()
|
let target_dists: HashMap<usize, usize> = self
|
||||||
|
.goals
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, g)| {
|
||||||
|
if let Goal::Opening(t) = g {
|
||||||
|
Some((*t, paths[self.locations[i]][*t].dist))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let new_goals: HashMap<usize, Vec<usize>> = self
|
||||||
|
.goals
|
||||||
|
.iter()
|
||||||
|
.positions(|g| matches!(g, Goal::Idle))
|
||||||
|
.map(|i| {
|
||||||
|
let loc = self.locations[i];
|
||||||
|
(
|
||||||
|
i,
|
||||||
|
self.interesting
|
||||||
|
.iter()
|
||||||
|
.filter_map(|target| {
|
||||||
|
let agent_dist = paths[loc][*target].dist;
|
||||||
|
if agent_dist + 2 >= self.time_remaining {
|
||||||
|
// Can't reach + open the valve as well as
|
||||||
|
// letting anything flow through
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lower_bound(&self) -> usize {
|
if let Some(dist) = target_dists.get(target) {
|
||||||
|
if *dist > agent_dist {
|
||||||
|
// Someone else is targeting this, but they're slower
|
||||||
|
Some(*target)
|
||||||
|
} else {
|
||||||
|
// Someone else is targeting this, and they're faster
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Noone else is targeting this
|
||||||
|
Some(*target)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if new_goals.len() == 1 {
|
||||||
|
let (i, targets) = new_goals.iter().next().unwrap();
|
||||||
|
if targets.is_empty() {
|
||||||
|
let mut state = self.clone();
|
||||||
|
state.goals[*i] = Goal::NothingReachable;
|
||||||
|
return vec![state];
|
||||||
|
}
|
||||||
|
|
||||||
|
targets
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let mut state = self.clone();
|
||||||
|
state.goals[*i] = Goal::Opening(*t);
|
||||||
|
state
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else if new_goals.len() == 2 {
|
||||||
|
let mut it = new_goals.iter();
|
||||||
|
let (i1, targets1) = it.next().unwrap();
|
||||||
|
let (i2, targets2) = it.next().unwrap();
|
||||||
|
if targets1.len() == 0 {
|
||||||
|
if targets2.len() == 0 {
|
||||||
|
let mut state = self.clone();
|
||||||
|
state.goals[*i1] = Goal::NothingReachable;
|
||||||
|
state.goals[*i2] = Goal::NothingReachable;
|
||||||
|
return vec![state];
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets2
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let mut state = self.clone();
|
||||||
|
state.goals[*i1] = Goal::NothingReachable;
|
||||||
|
state.goals[*i2] = Goal::Opening(*t);
|
||||||
|
state
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
} else if targets2.len() == 0 {
|
||||||
|
return targets1
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let mut state = self.clone();
|
||||||
|
state.goals[*i1] = Goal::Opening(*t);
|
||||||
|
state.goals[*i2] = Goal::NothingReachable;
|
||||||
|
state
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
} else if targets1.len() == 1 && targets1 == targets2 {
|
||||||
|
// Both agents can only reach one and the same goal
|
||||||
|
let mut state = self.clone();
|
||||||
|
let target = targets1[0];
|
||||||
|
if paths[self.locations[*i1]][target].dist < paths[self.locations[*i2]][target].dist
|
||||||
|
{
|
||||||
|
state.goals[*i1] = Goal::Opening(target);
|
||||||
|
state.goals[*i2] = Goal::NothingReachable;
|
||||||
|
} else {
|
||||||
|
state.goals[*i1] = Goal::NothingReachable;
|
||||||
|
state.goals[*i2] = Goal::Opening(target);
|
||||||
|
}
|
||||||
|
return vec![state];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
for target1 in targets1 {
|
||||||
|
for target2 in targets2 {
|
||||||
|
if target1 == target2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = self.clone();
|
||||||
|
state.goals[*i1] = Goal::Opening(*target1);
|
||||||
|
state.goals[*i2] = Goal::Opening(*target2);
|
||||||
|
result.push(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
unimplemented!("Can't find goals for >2 agents at the same time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtraArgs<'a> = (&'a Vec<Valve>, &'a Vec<Vec<Path>>);
|
||||||
|
impl util::BnBState<ExtraArgs<'_>> for State {
|
||||||
|
fn possible_actions(&self, (valves, paths): &ExtraArgs) -> Vec<State> {
|
||||||
|
assert!(!self.finished());
|
||||||
|
|
||||||
|
let goalless = self
|
||||||
|
.goals
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, g)| matches!(g, Goal::Idle))
|
||||||
|
.collect_vec();
|
||||||
|
if goalless.len() > 0 {
|
||||||
|
return self.find_goals(&(valves, paths));
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_goal_dist = self
|
||||||
|
.goals
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, g)| {
|
||||||
|
if let Goal::Opening(target) = g {
|
||||||
|
Some(paths[self.locations[i]][*target].dist)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.min()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let time_spent = min_goal_dist + 1;
|
||||||
|
let mut completed = self.clone();
|
||||||
|
completed.pressure_released += completed.flow_rate * time_spent;
|
||||||
|
completed.time_remaining -= time_spent;
|
||||||
|
for (i, goal) in self.goals.iter().enumerate() {
|
||||||
|
if let Goal::Opening(goal) = goal {
|
||||||
|
let path = &paths[self.locations[i]][*goal];
|
||||||
|
if path.dist == min_goal_dist {
|
||||||
|
completed.locations[i] = *goal;
|
||||||
|
completed.opened |= 1 << goal;
|
||||||
|
completed.goals[i] = Goal::Idle;
|
||||||
|
if completed.interesting.remove(goal) {
|
||||||
|
// Only increase flow rate if no other agent already
|
||||||
|
// opened this valve
|
||||||
|
completed.flow_rate += valves[*goal].flow_rate;
|
||||||
|
}
|
||||||
|
completed.history[i].push(*goal);
|
||||||
|
} else {
|
||||||
|
completed.locations[i] = path.path[time_spent - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vec![completed];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finished(&self) -> bool {
|
||||||
|
self.time_remaining == 0
|
||||||
|
|| self
|
||||||
|
.goals
|
||||||
|
.iter()
|
||||||
|
.all(|g| matches!(g, Goal::NothingReachable)) //self.interesting.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower_bound(&self, _: &ExtraArgs) -> usize {
|
||||||
self.pressure_released + self.flow_rate * self.time_remaining
|
self.pressure_released + self.flow_rate * self.time_remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upper_bound(&self, valves: &Vec<Valve>, dists: &Vec<Vec<usize>>) -> usize {
|
fn upper_bound(&self, (valves, paths): &ExtraArgs) -> usize {
|
||||||
let mut additional_flow = 0;
|
let mut additional_flow = 0;
|
||||||
for i in &self.interesting {
|
for i in &self.interesting {
|
||||||
let i = i.clone();
|
let i = i.clone();
|
||||||
if (self.opened & (1 << i)) != 0 {
|
if (self.opened & (1 << i)) != 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let dist = dists[self.location][i];
|
let dist = self
|
||||||
|
.locations
|
||||||
|
.iter()
|
||||||
|
.map(|loc| paths[*loc][i].dist)
|
||||||
|
.min()
|
||||||
|
.unwrap();
|
||||||
if dist < self.time_remaining {
|
if dist < self.time_remaining {
|
||||||
let flow = valves[i].flow_rate;
|
let flow = valves[i].flow_rate;
|
||||||
additional_flow += flow * (self.time_remaining - dist - 1);
|
additional_flow += flow * (self.time_remaining - dist - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lower_bound() + additional_flow
|
self.lower_bound(&(valves, paths)) + additional_flow
|
||||||
}
|
|
||||||
|
|
||||||
pub fn possible_actions(&self, valves: &Vec<Valve>, dists: &Vec<Vec<usize>>) -> Vec<State> {
|
|
||||||
assert!(!self.finished());
|
|
||||||
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
let loc = self.location;
|
|
||||||
|
|
||||||
if (self.opened & (1 << loc)) == 0 && self.interesting.contains(&loc) {
|
|
||||||
let mut open = self.clone();
|
|
||||||
open.opened |= 1 << loc;
|
|
||||||
// First increase pressure released, then the flow rate
|
|
||||||
open.pressure_released += open.flow_rate;
|
|
||||||
open.flow_rate += valves[loc].flow_rate;
|
|
||||||
open.time_remaining -= 1;
|
|
||||||
open.interesting.remove(&loc);
|
|
||||||
result.push(open);
|
|
||||||
}
|
|
||||||
|
|
||||||
for l in &self.interesting {
|
|
||||||
let i = l.clone();
|
|
||||||
let dist = dists[loc][i];
|
|
||||||
if dist == 0 || dist == usize::MAX || self.time_remaining < dist {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut moved = self.clone();
|
|
||||||
moved.location = i;
|
|
||||||
moved.pressure_released += moved.flow_rate * dist;
|
|
||||||
moved.time_remaining -= dist;
|
|
||||||
result.push(moved);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for State {
|
impl Hash for State {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.opened.hash(state);
|
self.opened.hash(state);
|
||||||
self.location.hash(state);
|
self.locations.hash(state);
|
||||||
|
self.goals.hash(state);
|
||||||
self.time_remaining.hash(state);
|
self.time_remaining.hash(state);
|
||||||
self.pressure_released.hash(state);
|
self.pressure_released.hash(state);
|
||||||
}
|
}
|
||||||
@ -177,26 +369,11 @@ impl Hash for State {
|
|||||||
impl PartialEq for State {
|
impl PartialEq for State {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.opened == other.opened
|
self.opened == other.opened
|
||||||
&& self.location == other.location
|
&& self.locations == other.locations
|
||||||
|
&& self.goals == other.goals
|
||||||
&& self.time_remaining == other.time_remaining
|
&& self.time_remaining == other.time_remaining
|
||||||
&& self.pressure_released == other.pressure_released
|
&& self.pressure_released == other.pressure_released
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for State {}
|
impl Eq for State {}
|
||||||
|
|
||||||
pub fn possible_actions(state: &State, valves: &Vec<Valve>, dists: &Vec<Vec<usize>>) -> Vec<State> {
|
|
||||||
lazy_static! {
|
|
||||||
static ref CACHE: Mutex<HashMap<State, Vec<State>>> = Mutex::new(HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
let actions = { CACHE.lock().unwrap().get(state).cloned() };
|
|
||||||
if let Some(actions) = actions {
|
|
||||||
// assert_eq!(actions, state.possible_actions(valves, dists));
|
|
||||||
actions.clone()
|
|
||||||
} else {
|
|
||||||
let mut cache = CACHE.lock().unwrap();
|
|
||||||
cache.insert(state.clone(), state.possible_actions(valves, dists));
|
|
||||||
cache[state].clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
240
src/day18.rs
Normal file
240
src/day18.rs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Material {
|
||||||
|
Air,
|
||||||
|
Lava,
|
||||||
|
Steam,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_droplet(input: &String) -> Vec<Vec<Vec<Material>>> {
|
||||||
|
let mut cubes: Vec<(usize, usize, usize)> = Vec::new();
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
if line.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sp = line.split(',');
|
||||||
|
cubes.push((
|
||||||
|
sp.next().unwrap().parse().unwrap(),
|
||||||
|
sp.next().unwrap().parse().unwrap(),
|
||||||
|
sp.next().unwrap().parse().unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (x_min, x_max) = cubes.iter().map(|c| c.0).minmax().into_option().unwrap();
|
||||||
|
let (y_min, y_max) = cubes.iter().map(|c| c.1).minmax().into_option().unwrap();
|
||||||
|
let (z_min, z_max) = cubes.iter().map(|c| c.2).minmax().into_option().unwrap();
|
||||||
|
let x_len = x_max - x_min + 3;
|
||||||
|
let y_len = y_max - y_min + 3;
|
||||||
|
let z_len = z_max - z_min + 3;
|
||||||
|
|
||||||
|
let mut result = vec![vec![vec![Material::Air; z_len]; y_len]; x_len];
|
||||||
|
for (x, y, z) in cubes {
|
||||||
|
result[x - x_min + 1][y - y_min + 1][z - z_min + 1] = Material::Lava;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn surface_area(droplet: &Vec<Vec<Vec<Material>>>) -> usize {
|
||||||
|
let x_len = droplet.len();
|
||||||
|
assert!(x_len > 0);
|
||||||
|
let y_len = droplet[0].len();
|
||||||
|
assert!(y_len > 0);
|
||||||
|
let z_len = droplet[0][0].len();
|
||||||
|
assert!(z_len > 0);
|
||||||
|
|
||||||
|
let up = (0..x_len)
|
||||||
|
.flat_map(|x| {
|
||||||
|
(0..y_len)
|
||||||
|
.map(move |y| (x, y, (0..z_len).collect()))
|
||||||
|
.collect_vec()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let down = (0..x_len)
|
||||||
|
.flat_map(|x| {
|
||||||
|
(0..y_len)
|
||||||
|
.map(move |y| (x, y, (0..z_len).rev().collect()))
|
||||||
|
.collect_vec()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let left = (0..x_len)
|
||||||
|
.flat_map(|x| {
|
||||||
|
(0..z_len)
|
||||||
|
.map(move |z| (x, z, (0..y_len).collect()))
|
||||||
|
.collect_vec()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let right = (0..x_len)
|
||||||
|
.flat_map(|x| {
|
||||||
|
(0..z_len)
|
||||||
|
.map(move |z| (x, z, (0..y_len).rev().collect()))
|
||||||
|
.collect_vec()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let fore = (0..y_len)
|
||||||
|
.flat_map(|y| {
|
||||||
|
(0..z_len)
|
||||||
|
.map(move |z| (y, z, (0..x_len).collect()))
|
||||||
|
.collect_vec()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let back = (0..y_len)
|
||||||
|
.flat_map(|y| {
|
||||||
|
(0..z_len)
|
||||||
|
.map(move |z| (y, z, (0..x_len).rev().collect()))
|
||||||
|
.collect_vec()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut result = 0;
|
||||||
|
result += surface_area_dir(droplet, &up, ArgOrder::XYZ);
|
||||||
|
result += surface_area_dir(droplet, &down, ArgOrder::XYZ);
|
||||||
|
result += surface_area_dir(droplet, &left, ArgOrder::XZY);
|
||||||
|
result += surface_area_dir(droplet, &right, ArgOrder::XZY);
|
||||||
|
result += surface_area_dir(droplet, &fore, ArgOrder::YZX);
|
||||||
|
result += surface_area_dir(droplet, &back, ArgOrder::YZX);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ArgOrder {
|
||||||
|
XYZ,
|
||||||
|
XZY,
|
||||||
|
YZX,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surface_area_dir(
|
||||||
|
droplet: &Vec<Vec<Vec<Material>>>,
|
||||||
|
lines: &Vec<(usize, usize, Vec<usize>)>,
|
||||||
|
order: ArgOrder,
|
||||||
|
) -> usize {
|
||||||
|
let mut result = 0;
|
||||||
|
for (a, b, c_range) in lines {
|
||||||
|
let mut last_empty = true;
|
||||||
|
for c in c_range {
|
||||||
|
let occupied = match order {
|
||||||
|
ArgOrder::XYZ => droplet[*a][*b][*c],
|
||||||
|
ArgOrder::XZY => droplet[*a][*c][*b],
|
||||||
|
ArgOrder::YZX => droplet[*c][*a][*b],
|
||||||
|
};
|
||||||
|
if last_empty && matches!(occupied, Material::Lava) {
|
||||||
|
result += 1;
|
||||||
|
}
|
||||||
|
last_empty = matches!(occupied, Material::Air);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outer_surface_area(droplet: &Vec<Vec<Vec<Material>>>) -> usize {
|
||||||
|
let x_len = droplet.len();
|
||||||
|
assert!(x_len > 0);
|
||||||
|
let y_len = droplet[0].len();
|
||||||
|
assert!(y_len > 0);
|
||||||
|
let z_len = droplet[0][0].len();
|
||||||
|
assert!(z_len > 0);
|
||||||
|
|
||||||
|
let mut next = Vec::new();
|
||||||
|
|
||||||
|
// top/bottom
|
||||||
|
for x in 0..x_len {
|
||||||
|
for y in 0..y_len {
|
||||||
|
next.push((x, y, 0));
|
||||||
|
next.push((x, y, z_len - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// left/right
|
||||||
|
for x in 0..x_len {
|
||||||
|
for z in 0..z_len {
|
||||||
|
next.push((x, 0, z));
|
||||||
|
next.push((x, y_len - 1, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fore/back
|
||||||
|
for y in 0..y_len {
|
||||||
|
for z in 0..z_len {
|
||||||
|
next.push((0, y, z));
|
||||||
|
next.push((x_len - 1, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut visited: HashSet<_> = next.iter().map(|c| c.clone()).collect();
|
||||||
|
let generators: [Box<dyn Fn(&Coord) -> Option<Coord>>; 6] = [
|
||||||
|
Box::new(|c| go_fore(c)),
|
||||||
|
Box::new(|c| go_back(c, x_len)),
|
||||||
|
Box::new(|c| go_left(c)),
|
||||||
|
Box::new(|c| go_right(c, y_len)),
|
||||||
|
Box::new(|c| go_up(c)),
|
||||||
|
Box::new(|c| go_down(c, z_len)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut result = 0;
|
||||||
|
while let Some(n) = next.pop().clone() {
|
||||||
|
for gen in &generators {
|
||||||
|
if let Some(c) = gen(&n) {
|
||||||
|
let occupied = droplet[c.0][c.1][c.2];
|
||||||
|
if matches!(occupied, Material::Air) && visited.insert(c) {
|
||||||
|
next.push(c);
|
||||||
|
} else if matches!(occupied, Material::Lava) {
|
||||||
|
result += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
type Coord = (usize, usize, usize);
|
||||||
|
|
||||||
|
fn go_fore(c: &Coord) -> Option<Coord> {
|
||||||
|
if c.0 > 0 {
|
||||||
|
Some((c.0 - 1, c.1, c.2))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_back(c: &Coord, x_len: usize) -> Option<Coord> {
|
||||||
|
if c.0 < x_len - 1 {
|
||||||
|
Some((c.0 + 1, c.1, c.2))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_left(c: &Coord) -> Option<Coord> {
|
||||||
|
if c.1 > 0 {
|
||||||
|
Some((c.0, c.1 - 1, c.2))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_right(c: &Coord, y_len: usize) -> Option<Coord> {
|
||||||
|
if c.1 < y_len - 1 {
|
||||||
|
Some((c.0, c.1 + 1, c.2))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_up(c: &Coord) -> Option<Coord> {
|
||||||
|
if c.2 > 0 {
|
||||||
|
Some((c.0, c.1, c.2 - 1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_down(c: &Coord, z_len: usize) -> Option<Coord> {
|
||||||
|
if c.2 < z_len - 1 {
|
||||||
|
Some((c.0, c.1, c.2 + 1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
210
src/day19.rs
Normal file
210
src/day19.rs
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::util::{self, BnBState};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Blueprint {
|
||||||
|
pub ore_ore_cost: usize,
|
||||||
|
pub clay_ore_cost: usize,
|
||||||
|
pub obsidian_ore_cost: usize,
|
||||||
|
pub obsidian_clay_cost: usize,
|
||||||
|
pub geode_ore_cost: usize,
|
||||||
|
pub geode_obsidian_cost: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_blueprints(input: &String) -> Vec<Blueprint> {
|
||||||
|
let re = Regex::new(
|
||||||
|
r"Blueprint (\d+): Each ore robot costs (\d+) ore. Each clay robot costs (\d+) ore. Each obsidian robot costs (\d+) ore and (\d+) clay. Each geode robot costs (\d+) ore and (\d+) obsidian.",
|
||||||
|
).unwrap();
|
||||||
|
input
|
||||||
|
.lines()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, line)| {
|
||||||
|
let captures = re
|
||||||
|
.captures(line)
|
||||||
|
.unwrap_or_else(|| panic!("Invalid blueprint formatting:\n{}", line));
|
||||||
|
assert_eq!(
|
||||||
|
captures.get(1).unwrap().as_str().parse::<usize>().unwrap(),
|
||||||
|
i + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
let ore_ore_cost = captures.get(2).unwrap().as_str().parse().unwrap();
|
||||||
|
let clay_ore_cost = captures.get(3).unwrap().as_str().parse().unwrap();
|
||||||
|
let obsidian_ore_cost = captures.get(4).unwrap().as_str().parse().unwrap();
|
||||||
|
let obsidian_clay_cost = captures.get(5).unwrap().as_str().parse().unwrap();
|
||||||
|
let geode_ore_cost = captures.get(6).unwrap().as_str().parse().unwrap();
|
||||||
|
let geode_obsidian_cost = captures.get(7).unwrap().as_str().parse().unwrap();
|
||||||
|
|
||||||
|
Blueprint {
|
||||||
|
ore_ore_cost,
|
||||||
|
clay_ore_cost,
|
||||||
|
obsidian_ore_cost,
|
||||||
|
obsidian_clay_cost,
|
||||||
|
geode_ore_cost,
|
||||||
|
geode_obsidian_cost,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct State {
|
||||||
|
pub time_remaining: usize,
|
||||||
|
|
||||||
|
pub ore: usize,
|
||||||
|
pub clay: usize,
|
||||||
|
pub obsidian: usize,
|
||||||
|
pub geodes: usize,
|
||||||
|
|
||||||
|
pub ore_robots: usize,
|
||||||
|
pub clay_robots: usize,
|
||||||
|
pub obsidian_robots: usize,
|
||||||
|
pub geode_robots: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new(initial_time: usize) -> State {
|
||||||
|
State {
|
||||||
|
time_remaining: initial_time,
|
||||||
|
ore: 0,
|
||||||
|
clay: 0,
|
||||||
|
obsidian: 0,
|
||||||
|
geodes: 0,
|
||||||
|
ore_robots: 1,
|
||||||
|
clay_robots: 0,
|
||||||
|
obsidian_robots: 0,
|
||||||
|
geode_robots: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn produce<F: Fn(&mut State)>(&self, time_for_ore_prod: isize, produce: F) -> Option<State> {
|
||||||
|
let time_until_robot_ready = (time_for_ore_prod.max(0) as usize) + 1;
|
||||||
|
// For this to make sense, we also need at least one minute
|
||||||
|
// remaining after producing the robot
|
||||||
|
if time_until_robot_ready + 1 > self.time_remaining {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut produced = self.clone();
|
||||||
|
produced.run_steps(time_until_robot_ready);
|
||||||
|
produce(&mut produced);
|
||||||
|
Some(produced)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn produce_ore_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||||
|
if self.ore_robots == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let time_required = num::Integer::div_ceil(
|
||||||
|
&((blueprint.ore_ore_cost as isize) - (self.ore as isize)),
|
||||||
|
&(self.ore_robots as isize),
|
||||||
|
);
|
||||||
|
self.produce(time_required, |mut s| {
|
||||||
|
s.ore_robots += 1;
|
||||||
|
s.ore -= blueprint.ore_ore_cost;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn produce_clay_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||||
|
if self.ore_robots == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let time_required = num::Integer::div_ceil(
|
||||||
|
&((blueprint.clay_ore_cost as isize) - (self.ore as isize)),
|
||||||
|
&(self.ore_robots as isize),
|
||||||
|
);
|
||||||
|
self.produce(time_required, |mut s| {
|
||||||
|
s.clay_robots += 1;
|
||||||
|
s.ore -= blueprint.clay_ore_cost;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn produce_obsidian_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||||
|
if self.ore_robots == 0 || self.clay_robots == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let time_required_ore = num::Integer::div_ceil(
|
||||||
|
&((blueprint.obsidian_ore_cost as isize) - (self.ore as isize)),
|
||||||
|
&(self.ore_robots as isize),
|
||||||
|
);
|
||||||
|
let time_required_clay = num::Integer::div_ceil(
|
||||||
|
&((blueprint.obsidian_clay_cost as isize) - (self.clay as isize)),
|
||||||
|
&(self.clay_robots as isize),
|
||||||
|
);
|
||||||
|
self.produce(time_required_ore.max(time_required_clay), |mut s| {
|
||||||
|
s.obsidian_robots += 1;
|
||||||
|
s.ore -= blueprint.obsidian_ore_cost;
|
||||||
|
s.clay -= blueprint.obsidian_clay_cost;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn produce_geode_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||||
|
if self.ore_robots == 0 || self.obsidian_robots == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let time_required_ore = num::Integer::div_ceil(
|
||||||
|
&((blueprint.geode_ore_cost as isize) - (self.ore as isize)),
|
||||||
|
&(self.ore_robots as isize),
|
||||||
|
);
|
||||||
|
let time_required_obsidian = num::Integer::div_ceil(
|
||||||
|
&((blueprint.geode_obsidian_cost as isize) - (self.obsidian as isize)),
|
||||||
|
&(self.obsidian_robots as isize),
|
||||||
|
);
|
||||||
|
self.produce(time_required_ore.max(time_required_obsidian), |mut s| {
|
||||||
|
s.geode_robots += 1;
|
||||||
|
s.ore -= blueprint.geode_ore_cost;
|
||||||
|
s.obsidian -= blueprint.geode_obsidian_cost;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_steps(&mut self, n: usize) {
|
||||||
|
assert!(self.time_remaining >= n);
|
||||||
|
|
||||||
|
self.time_remaining -= n;
|
||||||
|
self.ore += self.ore_robots * n;
|
||||||
|
self.clay += self.clay_robots * n;
|
||||||
|
self.obsidian += self.obsidian_robots * n;
|
||||||
|
self.geodes += self.geode_robots * n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl util::BnBState<Blueprint> for State {
|
||||||
|
fn finished(&self) -> bool {
|
||||||
|
self.time_remaining == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_actions(&self, blueprint: &Blueprint) -> Vec<State> {
|
||||||
|
assert!(!self.finished());
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
if self.time_remaining > 1 {
|
||||||
|
self.produce_ore_next(blueprint).map(|a| result.push(a));
|
||||||
|
self.produce_clay_next(blueprint).map(|a| result.push(a));
|
||||||
|
self.produce_obsidian_next(blueprint)
|
||||||
|
.map(|a| result.push(a));
|
||||||
|
self.produce_geode_next(blueprint).map(|a| result.push(a));
|
||||||
|
}
|
||||||
|
let mut do_nothing = self.clone();
|
||||||
|
do_nothing.run_steps(self.time_remaining);
|
||||||
|
result.push(do_nothing);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upper_bound(&self, b: &Blueprint) -> usize {
|
||||||
|
// Build one geode robot each remaining turn
|
||||||
|
// \sum_{k=1}^n {k - 1} = \sum_{k=0}^{n-1} {k} = 1/2 (n-1) n
|
||||||
|
self.lower_bound(b) + ((self.time_remaining - 1) * self.time_remaining) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower_bound(&self, _: &Blueprint) -> usize {
|
||||||
|
self.geodes + self.geode_robots * self.time_remaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_geodes(minutes: usize, blueprint: &Blueprint) -> usize {
|
||||||
|
let initial = State::new(minutes);
|
||||||
|
|
||||||
|
util::maximize(&initial, blueprint).lower_bound(blueprint)
|
||||||
|
}
|
||||||
122
src/day20.rs
Normal file
122
src/day20.rs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
use std::{cell::RefCell, fmt::Debug, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
|
pub struct Node {
|
||||||
|
pub val: isize,
|
||||||
|
pub next: Option<Rc<RefCell<Node>>>,
|
||||||
|
pub prev: Option<Rc<RefCell<Node>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Debug implementation will loop indefinitely, since we have a ring buffer
|
||||||
|
impl Debug for Node {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Node")
|
||||||
|
.field("val", &self.val)
|
||||||
|
.field("next", &self.next.as_ref().map(|n| n.borrow().val))
|
||||||
|
.field("prev", &self.prev.as_ref().map(|n| n.borrow().val))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_next(node: &Rc<RefCell<Node>>, n: usize) -> Rc<RefCell<Node>> {
|
||||||
|
let mut curr = node.clone();
|
||||||
|
for _ in 0..n {
|
||||||
|
let next = curr.borrow().next.as_ref().unwrap().clone();
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
curr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_prev(node: &Rc<RefCell<Node>>, n: usize) -> Rc<RefCell<Node>> {
|
||||||
|
let mut curr = node.clone();
|
||||||
|
for _ in 0..n {
|
||||||
|
let prev = curr.borrow().prev.as_ref().unwrap().clone();
|
||||||
|
curr = prev;
|
||||||
|
}
|
||||||
|
curr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_file(input: &String, key: isize) -> Vec<Rc<RefCell<Node>>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
let mut last = None;
|
||||||
|
for line in input.lines() {
|
||||||
|
let val: isize = line.parse().unwrap();
|
||||||
|
let node = Rc::new(RefCell::new(Node {
|
||||||
|
val: val * key,
|
||||||
|
next: None,
|
||||||
|
prev: last.clone(),
|
||||||
|
}));
|
||||||
|
if let Some(l) = last {
|
||||||
|
l.as_ref().borrow_mut().next = Some(node.clone());
|
||||||
|
}
|
||||||
|
result.push(node.clone());
|
||||||
|
last = Some(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(result.len() > 0);
|
||||||
|
|
||||||
|
let last_refcell = last.as_ref().unwrap().deref();
|
||||||
|
last_refcell.borrow_mut().next = Some(result[0].clone());
|
||||||
|
let first_refcell = &*result[0];
|
||||||
|
first_refcell.borrow_mut().prev = Some(last.as_ref().unwrap().clone());
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mix(nodes: &Vec<Rc<RefCell<Node>>>) {
|
||||||
|
let div = nodes.len() - 1;
|
||||||
|
|
||||||
|
for node in nodes {
|
||||||
|
let n = node.borrow().val;
|
||||||
|
if n == 0 || (n > 0 && (n as usize) % div == 0) || (n < 0 && (-n as usize) % div == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from current position
|
||||||
|
let prev = get_prev(node, 1);
|
||||||
|
let next = get_next(node, 1);
|
||||||
|
prev.borrow_mut().next = Some(next.clone());
|
||||||
|
next.borrow_mut().prev = Some(prev.clone());
|
||||||
|
|
||||||
|
let prev = if n > 0 {
|
||||||
|
let n = (n as usize) % div;
|
||||||
|
assert!(n < div);
|
||||||
|
get_next(node, n)
|
||||||
|
} else {
|
||||||
|
let n = ((-n as usize) % div) + 1;
|
||||||
|
assert!(n < div);
|
||||||
|
get_prev(node, n)
|
||||||
|
};
|
||||||
|
let next = get_next(&prev, 1);
|
||||||
|
prev.deref().borrow_mut().next = Some(node.clone());
|
||||||
|
next.deref().borrow_mut().prev = Some(node.clone());
|
||||||
|
|
||||||
|
let mut n_mut = node.deref().borrow_mut();
|
||||||
|
n_mut.prev = Some(prev.clone());
|
||||||
|
n_mut.next = Some(next.clone());
|
||||||
|
drop(n_mut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_nodes(nodes: &Vec<Rc<RefCell<Node>>>) {
|
||||||
|
let mut curr = nodes.iter().find(|n| n.borrow().val == 0).unwrap().clone();
|
||||||
|
loop {
|
||||||
|
print!("{}, ", curr.borrow().val);
|
||||||
|
|
||||||
|
let next = curr.borrow().next.as_ref().unwrap().clone();
|
||||||
|
curr = next;
|
||||||
|
if curr.borrow().val == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calc_coordinates(nodes: &Vec<Rc<RefCell<Node>>>) -> isize {
|
||||||
|
let zero = nodes.iter().find(|n| n.borrow().val == 0).unwrap();
|
||||||
|
let first = get_next(zero, 1000).borrow().val;
|
||||||
|
let second = get_next(zero, 2000).borrow().val;
|
||||||
|
let third = get_next(zero, 3000).borrow().val;
|
||||||
|
|
||||||
|
first + second + third
|
||||||
|
}
|
||||||
149
src/day21.rs
Normal file
149
src/day21.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
use trees::{tr, Node, Tree};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Operation {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
pub fn from(s: &str) -> Operation {
|
||||||
|
match s {
|
||||||
|
"+" => Operation::Add,
|
||||||
|
"-" => Operation::Sub,
|
||||||
|
"*" => Operation::Mul,
|
||||||
|
"/" => Operation::Div,
|
||||||
|
_ => panic!("Unknown operation {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum MonkeyKind {
|
||||||
|
Number(isize),
|
||||||
|
Calculate(String, String, Operation),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Monkey {
|
||||||
|
pub name: String,
|
||||||
|
pub kind: MonkeyKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_monkeys(input: &String) -> Tree<Monkey> {
|
||||||
|
let mut monkeys = HashMap::new();
|
||||||
|
|
||||||
|
let re = Regex::new(r"^(\w+): (?:(\d+)|(\w+) ([+\-*/]) (\w+))$").unwrap();
|
||||||
|
for line in input.lines() {
|
||||||
|
let captures = re.captures(line).unwrap();
|
||||||
|
let name = captures.get(1).unwrap().as_str();
|
||||||
|
let kind = if let Some(num) = captures.get(2) {
|
||||||
|
MonkeyKind::Number(num.as_str().parse().unwrap())
|
||||||
|
} else {
|
||||||
|
MonkeyKind::Calculate(
|
||||||
|
captures.get(3).unwrap().as_str().to_owned(),
|
||||||
|
captures.get(5).unwrap().as_str().to_owned(),
|
||||||
|
Operation::from(captures.get(4).unwrap().as_str()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
monkeys.insert(
|
||||||
|
name.to_owned(),
|
||||||
|
Monkey {
|
||||||
|
name: name.to_owned(),
|
||||||
|
kind,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = to_tree(&monkeys, "root");
|
||||||
|
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tree(monkeys: &HashMap<String, Monkey>, monkey: &str) -> Tree<Monkey> {
|
||||||
|
let monkey = &monkeys[monkey];
|
||||||
|
let mut tree = tr(monkey.clone());
|
||||||
|
|
||||||
|
if let MonkeyKind::Calculate(a, b, _) = &monkey.kind {
|
||||||
|
let forest = -to_tree(monkeys, a) - to_tree(monkeys, b);
|
||||||
|
tree.append(forest);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn yell(monkey: &Node<Monkey>, no_human: bool) -> isize {
|
||||||
|
if no_human {
|
||||||
|
assert!(monkey.data().name != "humn");
|
||||||
|
}
|
||||||
|
|
||||||
|
match &monkey.data().kind {
|
||||||
|
MonkeyKind::Number(n) => *n,
|
||||||
|
MonkeyKind::Calculate(_, _, op) => {
|
||||||
|
let mut children = monkey.iter();
|
||||||
|
let a = children.next().unwrap();
|
||||||
|
let b = children.next().unwrap();
|
||||||
|
match op {
|
||||||
|
Operation::Add => yell(a, no_human) + yell(b, no_human),
|
||||||
|
Operation::Sub => yell(a, no_human) - yell(b, no_human),
|
||||||
|
Operation::Mul => yell(a, no_human) * yell(b, no_human),
|
||||||
|
Operation::Div => yell(a, no_human) / yell(b, no_human),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_solution(target: &Node<Monkey>) -> isize {
|
||||||
|
let parent = target.parent().unwrap();
|
||||||
|
let other_pos = parent.iter().position(|n| n != target).unwrap();
|
||||||
|
assert!(other_pos < 2);
|
||||||
|
let sibling = parent.iter().nth(other_pos).unwrap();
|
||||||
|
let other = yell(sibling, true);
|
||||||
|
|
||||||
|
if parent.parent().is_none() {
|
||||||
|
other
|
||||||
|
} else {
|
||||||
|
let parent_target = find_solution(parent);
|
||||||
|
if let MonkeyKind::Calculate(_, _, op) = parent.data().kind {
|
||||||
|
match op {
|
||||||
|
Operation::Add => parent_target - other,
|
||||||
|
Operation::Sub => {
|
||||||
|
if other_pos == 0 {
|
||||||
|
other - parent_target
|
||||||
|
} else {
|
||||||
|
other + parent_target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Operation::Mul => parent_target / other,
|
||||||
|
Operation::Div => {
|
||||||
|
if other_pos == 0 {
|
||||||
|
other / parent_target
|
||||||
|
} else {
|
||||||
|
parent_target * other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Parent does not calculate!?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find<'a>(monkeys: &'a Tree<Monkey>, name: &str) -> Option<&'a Node<Monkey>> {
|
||||||
|
let mut next = vec![monkeys.root()];
|
||||||
|
|
||||||
|
while let Some(n) = next.pop() {
|
||||||
|
let data = n.data();
|
||||||
|
if data.name == name {
|
||||||
|
return Some(n);
|
||||||
|
}
|
||||||
|
next.extend(n.iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
611
src/day22.rs
Normal file
611
src/day22.rs
Normal file
@ -0,0 +1,611 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::util::Dir;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TurnDir {
|
||||||
|
CW,
|
||||||
|
CCW,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PassInstr {
|
||||||
|
Move(usize),
|
||||||
|
Turn(TurnDir),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Pose {
|
||||||
|
pub pos: (usize, usize),
|
||||||
|
pub orientation: Dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MoveResult {
|
||||||
|
Blocked,
|
||||||
|
BlockedByWrapping,
|
||||||
|
WrappedAround,
|
||||||
|
Success,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MoveResult {
|
||||||
|
pub fn was_blocked(&self) -> bool {
|
||||||
|
matches!(self, MoveResult::Blocked | MoveResult::BlockedByWrapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrapped_around(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
MoveResult::WrappedAround | MoveResult::BlockedByWrapping
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Navigable {
|
||||||
|
type Pose;
|
||||||
|
type NavigationInstruction;
|
||||||
|
|
||||||
|
fn forward(&self, from: &Self::Pose, steps: usize) -> (Self::Pose, MoveResult);
|
||||||
|
fn exec_instruction(
|
||||||
|
&self,
|
||||||
|
from: &Self::Pose,
|
||||||
|
inst: &Self::NavigationInstruction,
|
||||||
|
) -> (Self::Pose, MoveResult);
|
||||||
|
fn initial_pose(&self) -> Self::Pose;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SparseGrid {
|
||||||
|
pub grid: Vec<Vec<Option<bool>>>,
|
||||||
|
pub height: usize,
|
||||||
|
pub width: usize,
|
||||||
|
pub row_bounds: Vec<(usize, usize)>,
|
||||||
|
pub col_bounds: Vec<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SparseGrid {
|
||||||
|
fn print(&self, pose: Option<&Pose>) {
|
||||||
|
let check_pos = |y, x| {
|
||||||
|
if let Some(pose) = pose {
|
||||||
|
pose.pos.0 == y && pose.pos.1 == x
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (y, row) in self.grid.iter().enumerate() {
|
||||||
|
for (x, col) in row.iter().enumerate() {
|
||||||
|
let c = if check_pos(y, x) {
|
||||||
|
assert!(*col == Some(false));
|
||||||
|
match pose.unwrap().orientation {
|
||||||
|
Dir::Right => '>',
|
||||||
|
Dir::Down => 'v',
|
||||||
|
Dir::Left => '<',
|
||||||
|
Dir::Up => '^',
|
||||||
|
}
|
||||||
|
} else if col.is_none() {
|
||||||
|
' '
|
||||||
|
} else if *col == Some(true) {
|
||||||
|
'#'
|
||||||
|
} else {
|
||||||
|
'.'
|
||||||
|
};
|
||||||
|
print!("{}", c);
|
||||||
|
}
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Navigable for SparseGrid {
|
||||||
|
type Pose = Pose;
|
||||||
|
type NavigationInstruction = PassInstr;
|
||||||
|
|
||||||
|
fn forward(&self, from: &Pose, steps: usize) -> (Pose, MoveResult) {
|
||||||
|
let mut pose = from.clone();
|
||||||
|
let pos = &mut pose.pos;
|
||||||
|
let mut status = MoveResult::Success;
|
||||||
|
for _ in 0..steps {
|
||||||
|
let next_pos = match from.orientation {
|
||||||
|
Dir::Left => {
|
||||||
|
let bounds = &self.row_bounds[pos.0];
|
||||||
|
let next_col = if pos.1 == 0 || pos.1 == bounds.0 {
|
||||||
|
status = MoveResult::WrappedAround;
|
||||||
|
bounds.1
|
||||||
|
} else {
|
||||||
|
pos.1 - 1
|
||||||
|
};
|
||||||
|
(pos.0, next_col)
|
||||||
|
}
|
||||||
|
Dir::Up => {
|
||||||
|
let bounds = &self.col_bounds[pos.1];
|
||||||
|
let next_row = if pos.0 == 0 || pos.0 == bounds.0 {
|
||||||
|
status = MoveResult::WrappedAround;
|
||||||
|
bounds.1
|
||||||
|
} else {
|
||||||
|
pos.0 - 1
|
||||||
|
};
|
||||||
|
(next_row, pos.1)
|
||||||
|
}
|
||||||
|
Dir::Right => {
|
||||||
|
let bounds = &self.row_bounds[pos.0];
|
||||||
|
let next_col = if pos.1 == bounds.1 {
|
||||||
|
status = MoveResult::WrappedAround;
|
||||||
|
bounds.0
|
||||||
|
} else {
|
||||||
|
pos.1 + 1
|
||||||
|
};
|
||||||
|
(pos.0, next_col)
|
||||||
|
}
|
||||||
|
Dir::Down => {
|
||||||
|
let bounds = &self.col_bounds[pos.1];
|
||||||
|
let next_row = if pos.0 == bounds.1 {
|
||||||
|
status = MoveResult::WrappedAround;
|
||||||
|
bounds.0
|
||||||
|
} else {
|
||||||
|
pos.0 + 1
|
||||||
|
};
|
||||||
|
(next_row, pos.1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.grid[next_pos.0][next_pos.1].unwrap() {
|
||||||
|
// We're about to hit a wall
|
||||||
|
if status.wrapped_around() {
|
||||||
|
return (pose, MoveResult::BlockedByWrapping);
|
||||||
|
} else {
|
||||||
|
return (pose, MoveResult::Blocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*pos = next_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
(pose, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec_instruction(
|
||||||
|
&self,
|
||||||
|
from: &Self::Pose,
|
||||||
|
inst: &Self::NavigationInstruction,
|
||||||
|
) -> (Self::Pose, MoveResult) {
|
||||||
|
match inst {
|
||||||
|
PassInstr::Move(n) => self.forward(from, *n),
|
||||||
|
PassInstr::Turn(dir) => (
|
||||||
|
Pose {
|
||||||
|
pos: from.pos,
|
||||||
|
orientation: match dir {
|
||||||
|
TurnDir::CW => from.orientation.cw(),
|
||||||
|
TurnDir::CCW => from.orientation.ccw(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MoveResult::Success,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_pose(&self) -> Self::Pose {
|
||||||
|
let col = self.grid[0]
|
||||||
|
.iter()
|
||||||
|
.position(|t| if let Some(occ) = t { !occ } else { false })
|
||||||
|
.unwrap();
|
||||||
|
Pose {
|
||||||
|
pos: (0, col),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum CubeSide {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Fore,
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CubeGrid {
|
||||||
|
// The side grids don't have to be sparse, but we don't have a dense
|
||||||
|
// implementation...
|
||||||
|
pub sides: HashMap<CubeSide, SparseGrid>,
|
||||||
|
|
||||||
|
pub side_height: usize,
|
||||||
|
pub side_width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CubeGrid {
|
||||||
|
pub fn from(sparse: &SparseGrid, pattern: &HashMap<CubeSide, (usize, usize)>) -> CubeGrid {
|
||||||
|
let fold_width = pattern.values().map(|(_, col)| col).max().unwrap() + 1;
|
||||||
|
let fold_height = pattern.values().map(|(row, _)| row).max().unwrap() + 1;
|
||||||
|
let side_width = sparse.width / fold_width;
|
||||||
|
assert!(sparse.width % fold_width == 0);
|
||||||
|
let side_height = sparse.height / fold_height;
|
||||||
|
assert!(sparse.height % fold_height == 0);
|
||||||
|
|
||||||
|
let mut sides = HashMap::new();
|
||||||
|
for (side, (fold_row, fold_col)) in pattern {
|
||||||
|
let mut grid = Vec::new();
|
||||||
|
for r in 0..side_height {
|
||||||
|
let mut row = Vec::new();
|
||||||
|
for c in 0..side_width {
|
||||||
|
let tile = sparse.grid[fold_row * side_height + r][fold_col * side_width + c];
|
||||||
|
assert!(tile.is_some());
|
||||||
|
row.push(tile);
|
||||||
|
}
|
||||||
|
grid.push(row);
|
||||||
|
}
|
||||||
|
let grid = SparseGrid {
|
||||||
|
grid,
|
||||||
|
height: side_height,
|
||||||
|
width: side_width,
|
||||||
|
row_bounds: vec![(0, side_width - 1); side_height],
|
||||||
|
col_bounds: vec![(0, side_height - 1); side_width],
|
||||||
|
};
|
||||||
|
assert!(sides.insert(*side, grid).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
CubeGrid {
|
||||||
|
sides,
|
||||||
|
side_height,
|
||||||
|
side_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(&self, pose: &(CubeSide, Pose)) {
|
||||||
|
for (side, grid) in &self.sides {
|
||||||
|
if pose.0 != *side {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
println!("{:?}:", side);
|
||||||
|
grid.print(Some(&pose.1));
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_around(&self, side: CubeSide, pose: &Pose) -> (CubeSide, Pose) {
|
||||||
|
// FIXME: This is hardcoded for the folding pattern of the actual input.
|
||||||
|
// It won't work for the example input.
|
||||||
|
assert_eq!(self.side_height, self.side_width);
|
||||||
|
let dir = pose.orientation;
|
||||||
|
let pos = pose.pos;
|
||||||
|
let row = pos.0;
|
||||||
|
let last = self.side_height - 1;
|
||||||
|
let row_inv = last - row;
|
||||||
|
let col = pos.1;
|
||||||
|
match side {
|
||||||
|
CubeSide::Top => match dir {
|
||||||
|
Dir::Right => (
|
||||||
|
CubeSide::Right,
|
||||||
|
Pose {
|
||||||
|
pos: (row, 0),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Down => (
|
||||||
|
CubeSide::Fore,
|
||||||
|
Pose {
|
||||||
|
pos: (0, col),
|
||||||
|
orientation: Dir::Down,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Left => (
|
||||||
|
CubeSide::Left,
|
||||||
|
Pose {
|
||||||
|
pos: (row_inv, 0),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Up => (
|
||||||
|
CubeSide::Back,
|
||||||
|
Pose {
|
||||||
|
pos: (col, 0),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CubeSide::Bottom => match dir {
|
||||||
|
Dir::Right => (
|
||||||
|
CubeSide::Right,
|
||||||
|
Pose {
|
||||||
|
pos: (row_inv, last),
|
||||||
|
orientation: Dir::Left,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Down => (
|
||||||
|
CubeSide::Back,
|
||||||
|
Pose {
|
||||||
|
pos: (col, last),
|
||||||
|
orientation: Dir::Left,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Left => (
|
||||||
|
CubeSide::Left,
|
||||||
|
Pose {
|
||||||
|
pos: (row, last),
|
||||||
|
orientation: Dir::Left,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Up => (
|
||||||
|
CubeSide::Fore,
|
||||||
|
Pose {
|
||||||
|
pos: (last, col),
|
||||||
|
orientation: Dir::Up,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CubeSide::Left => match dir {
|
||||||
|
Dir::Right => (
|
||||||
|
CubeSide::Bottom,
|
||||||
|
Pose {
|
||||||
|
pos: (row, 0),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Down => (
|
||||||
|
CubeSide::Back,
|
||||||
|
Pose {
|
||||||
|
pos: (0, col),
|
||||||
|
orientation: Dir::Down,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Left => (
|
||||||
|
CubeSide::Top,
|
||||||
|
Pose {
|
||||||
|
pos: (row_inv, 0),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Up => (
|
||||||
|
CubeSide::Fore,
|
||||||
|
Pose {
|
||||||
|
pos: (col, 0),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CubeSide::Right => match dir {
|
||||||
|
Dir::Right => (
|
||||||
|
CubeSide::Bottom,
|
||||||
|
Pose {
|
||||||
|
pos: (row_inv, last),
|
||||||
|
orientation: Dir::Left,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Down => (
|
||||||
|
CubeSide::Fore,
|
||||||
|
Pose {
|
||||||
|
pos: (col, last),
|
||||||
|
orientation: Dir::Left,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Left => (
|
||||||
|
CubeSide::Top,
|
||||||
|
Pose {
|
||||||
|
pos: (row, last),
|
||||||
|
orientation: Dir::Left,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Up => (
|
||||||
|
CubeSide::Back,
|
||||||
|
Pose {
|
||||||
|
pos: (last, col),
|
||||||
|
orientation: Dir::Up,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CubeSide::Fore => match dir {
|
||||||
|
Dir::Right => (
|
||||||
|
CubeSide::Right,
|
||||||
|
Pose {
|
||||||
|
pos: (last, row),
|
||||||
|
orientation: Dir::Up,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Down => (
|
||||||
|
CubeSide::Bottom,
|
||||||
|
Pose {
|
||||||
|
pos: (0, col),
|
||||||
|
orientation: Dir::Down,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Left => (
|
||||||
|
CubeSide::Left,
|
||||||
|
Pose {
|
||||||
|
pos: (0, row),
|
||||||
|
orientation: Dir::Down,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Up => (
|
||||||
|
CubeSide::Top,
|
||||||
|
Pose {
|
||||||
|
pos: (last, col),
|
||||||
|
orientation: Dir::Up,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CubeSide::Back => match dir {
|
||||||
|
Dir::Right => (
|
||||||
|
CubeSide::Bottom,
|
||||||
|
Pose {
|
||||||
|
pos: (last, row),
|
||||||
|
orientation: Dir::Up,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Down => (
|
||||||
|
CubeSide::Right,
|
||||||
|
Pose {
|
||||||
|
pos: (0, col),
|
||||||
|
orientation: Dir::Down,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Left => (
|
||||||
|
CubeSide::Top,
|
||||||
|
Pose {
|
||||||
|
pos: (0, row),
|
||||||
|
orientation: Dir::Down,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Dir::Up => (
|
||||||
|
CubeSide::Left,
|
||||||
|
Pose {
|
||||||
|
pos: (last, col),
|
||||||
|
orientation: Dir::Up,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Navigable for CubeGrid {
|
||||||
|
type Pose = (CubeSide, Pose);
|
||||||
|
type NavigationInstruction = PassInstr;
|
||||||
|
|
||||||
|
fn forward(
|
||||||
|
&self,
|
||||||
|
(from_side, from_pose): &Self::Pose,
|
||||||
|
steps: usize,
|
||||||
|
) -> (Self::Pose, MoveResult) {
|
||||||
|
let mut side = from_side.clone();
|
||||||
|
let mut pose = from_pose.clone();
|
||||||
|
let mut status = MoveResult::Success;
|
||||||
|
for _ in 0..steps {
|
||||||
|
let (next_pose, res) = self.sides[&side].forward(&pose, 1);
|
||||||
|
if res.wrapped_around() {
|
||||||
|
let (next_side, next_pose) = self.wrap_around(side, &pose);
|
||||||
|
if self.sides[&next_side].grid[next_pose.pos.0][next_pose.pos.1] == Some(true) {
|
||||||
|
// Can't wrap around, we're blocked
|
||||||
|
return ((side, pose), MoveResult::BlockedByWrapping);
|
||||||
|
}
|
||||||
|
side = next_side;
|
||||||
|
pose = next_pose;
|
||||||
|
status = MoveResult::WrappedAround;
|
||||||
|
} else if res.was_blocked() {
|
||||||
|
return ((side, pose), MoveResult::Blocked);
|
||||||
|
} else {
|
||||||
|
pose = next_pose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((side, pose), status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec_instruction(
|
||||||
|
&self,
|
||||||
|
from: &Self::Pose,
|
||||||
|
inst: &Self::NavigationInstruction,
|
||||||
|
) -> (Self::Pose, MoveResult) {
|
||||||
|
match inst {
|
||||||
|
PassInstr::Move(n) => self.forward(from, *n),
|
||||||
|
PassInstr::Turn(dir) => (
|
||||||
|
(
|
||||||
|
from.0,
|
||||||
|
Pose {
|
||||||
|
pos: from.1.pos,
|
||||||
|
orientation: match dir {
|
||||||
|
TurnDir::CW => from.1.orientation.cw(),
|
||||||
|
TurnDir::CCW => from.1.orientation.ccw(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MoveResult::Success,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_pose(&self) -> Self::Pose {
|
||||||
|
let col = self.sides[&CubeSide::Top].grid[0]
|
||||||
|
.iter()
|
||||||
|
.position(|t| *t == Some(false))
|
||||||
|
.unwrap();
|
||||||
|
(
|
||||||
|
CubeSide::Top,
|
||||||
|
Pose {
|
||||||
|
pos: (0, col),
|
||||||
|
orientation: Dir::Right,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_map_and_path(input: &str) -> (SparseGrid, Vec<PassInstr>) {
|
||||||
|
let lines = input.lines().collect_vec();
|
||||||
|
|
||||||
|
let mut grid: Vec<Vec<Option<bool>>> = Vec::new();
|
||||||
|
let mut width = 0;
|
||||||
|
for line in &lines[..lines.len() - 2] {
|
||||||
|
width = width.max(line.len());
|
||||||
|
let grid_line = line
|
||||||
|
.chars()
|
||||||
|
.map(|c| match c {
|
||||||
|
' ' => None,
|
||||||
|
'.' => Some(false),
|
||||||
|
'#' => Some(true),
|
||||||
|
_ => panic!("Unknown grid character {}", c),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
grid.push(grid_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
let height = grid.len();
|
||||||
|
let mut row_bounds = vec![(0, 0); height];
|
||||||
|
for row in 0..height {
|
||||||
|
let row_width = grid[row].len();
|
||||||
|
if row_width < width {
|
||||||
|
grid[row].extend((row_width..width).map(|_| None));
|
||||||
|
}
|
||||||
|
let lower = grid[row].iter().position(Option::is_some).unwrap();
|
||||||
|
let upper = width - 1 - grid[row].iter().rev().position(Option::is_some).unwrap();
|
||||||
|
row_bounds[row] = (lower, upper);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut col_bounds = vec![(0, 0); width];
|
||||||
|
for col in 0..width {
|
||||||
|
let col_it = (0..height).map(|row| grid[row][col]);
|
||||||
|
let lower = col_it.clone().position(|t| t.is_some()).unwrap();
|
||||||
|
let upper = height - 1 - col_it.rev().position(|t| t.is_some()).unwrap();
|
||||||
|
col_bounds[col] = (lower, upper);
|
||||||
|
}
|
||||||
|
|
||||||
|
let grid = SparseGrid {
|
||||||
|
grid,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
row_bounds,
|
||||||
|
col_bounds,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let mut inst_begin = 0;
|
||||||
|
let inst_line = lines[lines.len() - 1].as_bytes();
|
||||||
|
while inst_begin < inst_line.len() {
|
||||||
|
let mut i = inst_begin;
|
||||||
|
let c = inst_line[i];
|
||||||
|
if c.is_ascii_digit() {
|
||||||
|
while i < inst_line.len() && inst_line[i].is_ascii_digit() {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
let dist = std::str::from_utf8(&inst_line[inst_begin..i])
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
instructions.push(PassInstr::Move(dist));
|
||||||
|
inst_begin = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = match c {
|
||||||
|
b'L' => TurnDir::CCW,
|
||||||
|
b'R' => TurnDir::CW,
|
||||||
|
_ => panic!("Unknown turn direction {}", c),
|
||||||
|
};
|
||||||
|
instructions.push(PassInstr::Turn(dir));
|
||||||
|
inst_begin += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(grid, instructions)
|
||||||
|
}
|
||||||
112
src/day23.rs
Normal file
112
src/day23.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::util::{self, SignedCoord};
|
||||||
|
|
||||||
|
pub fn parse_map(input: &str) -> HashSet<SignedCoord> {
|
||||||
|
let mut result = HashSet::new();
|
||||||
|
for (y, row) in input.lines().enumerate() {
|
||||||
|
for (x, c) in row.chars().enumerate() {
|
||||||
|
if c == '#' {
|
||||||
|
result.insert((y as isize, x as isize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUM_DIRS: usize = 4;
|
||||||
|
|
||||||
|
pub fn do_round(elves: &mut HashSet<SignedCoord>, it: usize) -> bool {
|
||||||
|
let mut proposals: HashMap<SignedCoord, Vec<SignedCoord>> = HashMap::new();
|
||||||
|
|
||||||
|
let mut elves_too_close = false;
|
||||||
|
for elf in elves.iter() {
|
||||||
|
if noone_adjacent(elves, elf) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elves_too_close = true;
|
||||||
|
for offset in 0..NUM_DIRS {
|
||||||
|
let dir = (it + offset) % NUM_DIRS;
|
||||||
|
if let Some(new_loc) = propose(elves, elf, dir) {
|
||||||
|
proposals.entry(new_loc).or_insert_with(Vec::new).push(*elf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (target, interested) in proposals {
|
||||||
|
if interested.len() == 1 {
|
||||||
|
elves.remove(&interested[0]);
|
||||||
|
elves.insert(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elves_too_close
|
||||||
|
}
|
||||||
|
|
||||||
|
fn noone_adjacent(elves: &HashSet<SignedCoord>, elf: &SignedCoord) -> bool {
|
||||||
|
!(elves.contains(&(elf.0, elf.1 + 1))
|
||||||
|
|| elves.contains(&(elf.0, elf.1 - 1))
|
||||||
|
|| elves.contains(&(elf.0 + 1, elf.1))
|
||||||
|
|| elves.contains(&(elf.0 + 1, elf.1 + 1))
|
||||||
|
|| elves.contains(&(elf.0 + 1, elf.1 - 1))
|
||||||
|
|| elves.contains(&(elf.0 - 1, elf.1))
|
||||||
|
|| elves.contains(&(elf.0 - 1, elf.1 + 1))
|
||||||
|
|| elves.contains(&(elf.0 - 1, elf.1 - 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propose(elves: &HashSet<SignedCoord>, elf: &SignedCoord, dir: usize) -> Option<SignedCoord> {
|
||||||
|
let (moved, other) = match dir {
|
||||||
|
0 => (
|
||||||
|
(elf.0 - 1, elf.1),
|
||||||
|
((elf.0 - 1, elf.1 - 1), (elf.0 - 1, elf.1 + 1)),
|
||||||
|
), // N
|
||||||
|
1 => (
|
||||||
|
(elf.0 + 1, elf.1),
|
||||||
|
((elf.0 + 1, elf.1 - 1), (elf.0 + 1, elf.1 + 1)),
|
||||||
|
), // S
|
||||||
|
2 => (
|
||||||
|
(elf.0, elf.1 - 1),
|
||||||
|
((elf.0 - 1, elf.1 - 1), (elf.0 + 1, elf.1 - 1)),
|
||||||
|
), // E
|
||||||
|
3 => (
|
||||||
|
(elf.0, elf.1 + 1),
|
||||||
|
((elf.0 - 1, elf.1 + 1), (elf.0 + 1, elf.1 + 1)),
|
||||||
|
), // W
|
||||||
|
_ => panic!("Unknown direction {}", dir),
|
||||||
|
};
|
||||||
|
if elves.contains(&moved) || elves.contains(&other.0) || elves.contains(&other.1) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(moved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_map(elves: &HashSet<SignedCoord>) {
|
||||||
|
let (y_min, y_max) = util::minmax_unwrap(&elves.iter().map(|c| c.0).minmax());
|
||||||
|
let (x_min, x_max) = util::minmax_unwrap(&elves.iter().map(|c| c.1).minmax());
|
||||||
|
|
||||||
|
println!("({}, {})", y_min, x_min);
|
||||||
|
for y in y_min..(y_max + 1) {
|
||||||
|
for x in x_min..(x_max + 1) {
|
||||||
|
if elves.contains(&(y, x)) {
|
||||||
|
print!("#");
|
||||||
|
} else {
|
||||||
|
print!(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty_ground(elves: &HashSet<SignedCoord>) -> usize {
|
||||||
|
let (y_min, y_max) = util::minmax_unwrap(&elves.iter().map(|c| c.0).minmax());
|
||||||
|
let (x_min, x_max) = util::minmax_unwrap(&elves.iter().map(|c| c.1).minmax());
|
||||||
|
let y_dim = (y_max + 1 - y_min) as usize;
|
||||||
|
let x_dim = (x_max + 1 - x_min) as usize;
|
||||||
|
|
||||||
|
y_dim * x_dim - elves.len()
|
||||||
|
}
|
||||||
188
src/day24.rs
Normal file
188
src/day24.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::util::{Coord, Dir};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Tile {
|
||||||
|
Empty,
|
||||||
|
Wall,
|
||||||
|
Blizzards(Vec<Dir>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns (map, start, end)
|
||||||
|
pub fn parse_map(input: &str) -> (Vec<Vec<Tile>>, Coord, Coord) {
|
||||||
|
let mut map = Vec::new();
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
let mut row = Vec::new();
|
||||||
|
|
||||||
|
for c in line.chars() {
|
||||||
|
row.push(match c {
|
||||||
|
'.' => Tile::Empty,
|
||||||
|
'#' => Tile::Wall,
|
||||||
|
_ => Tile::Blizzards(vec![Dir::from_char(c).unwrap()]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
map.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(map.len() >= 2);
|
||||||
|
let last_row = map.len() - 1;
|
||||||
|
|
||||||
|
let start = map[0]
|
||||||
|
.iter()
|
||||||
|
.position(|t| matches!(t, Tile::Empty))
|
||||||
|
.unwrap();
|
||||||
|
let end = map[last_row]
|
||||||
|
.iter()
|
||||||
|
.position(|t| matches!(t, Tile::Empty))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(map, (0, start), (last_row, end))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_map(map: &Vec<Vec<Tile>>, expedition: &Coord) {
|
||||||
|
for (y, row) in map.iter().enumerate() {
|
||||||
|
for (x, tile) in row.iter().enumerate() {
|
||||||
|
if *expedition == (y, x) {
|
||||||
|
print!("E");
|
||||||
|
} else {
|
||||||
|
match tile {
|
||||||
|
Tile::Empty => print!("."),
|
||||||
|
Tile::Wall => print!("#"),
|
||||||
|
Tile::Blizzards(blizzards) => {
|
||||||
|
if blizzards.len() > 1 {
|
||||||
|
print!("{}", blizzards.len());
|
||||||
|
} else {
|
||||||
|
print!("{}", blizzards[0].to_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_map(map: &Vec<Vec<Tile>>) -> Vec<Vec<Tile>> {
|
||||||
|
assert!(map.len() > 0);
|
||||||
|
// Outermost rows/columns are walls
|
||||||
|
let min_y = 1;
|
||||||
|
let max_y = map.len() - 2;
|
||||||
|
let min_x = 1;
|
||||||
|
let max_x = map[0].len() - 2;
|
||||||
|
|
||||||
|
// Start with a map without blizzards
|
||||||
|
let mut result: Vec<Vec<Tile>> = map
|
||||||
|
.iter()
|
||||||
|
.map(|r| {
|
||||||
|
r.iter()
|
||||||
|
.map(|t| {
|
||||||
|
if matches!(t, Tile::Blizzards(_)) {
|
||||||
|
Tile::Empty
|
||||||
|
} else {
|
||||||
|
t.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for y in 0..map.len() {
|
||||||
|
for x in 0..map[0].len() {
|
||||||
|
if let Tile::Blizzards(blizzards) = &map[y][x] {
|
||||||
|
for blizzard_dir in blizzards {
|
||||||
|
let new_tile = match blizzard_dir {
|
||||||
|
Dir::Right => {
|
||||||
|
if x == max_x {
|
||||||
|
(y, min_x)
|
||||||
|
} else {
|
||||||
|
(y, x + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dir::Down => {
|
||||||
|
if y == max_y {
|
||||||
|
(min_y, x)
|
||||||
|
} else {
|
||||||
|
(y + 1, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dir::Left => {
|
||||||
|
if x == min_x {
|
||||||
|
(y, max_x)
|
||||||
|
} else {
|
||||||
|
(y, x - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dir::Up => {
|
||||||
|
if y == min_y {
|
||||||
|
(max_y, x)
|
||||||
|
} else {
|
||||||
|
(y - 1, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_tile = &mut result[new_tile.0][new_tile.1];
|
||||||
|
if let Tile::Blizzards(blizzards) = new_tile {
|
||||||
|
blizzards.push(*blizzard_dir);
|
||||||
|
} else {
|
||||||
|
assert!(matches!(new_tile, Tile::Empty));
|
||||||
|
*new_tile = Tile::Blizzards(vec![*blizzard_dir]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns length of the shortest path
|
||||||
|
pub fn find_path(
|
||||||
|
initial: &Vec<Vec<Tile>>,
|
||||||
|
start: &Coord,
|
||||||
|
target: &Coord,
|
||||||
|
) -> (usize, Vec<Vec<Tile>>) {
|
||||||
|
let height = initial.len();
|
||||||
|
assert!(height > 0);
|
||||||
|
let width = initial[0].len();
|
||||||
|
|
||||||
|
let mut map = initial.clone();
|
||||||
|
let mut to_check = HashSet::new();
|
||||||
|
to_check.insert(*start);
|
||||||
|
let mut rounds = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
map = next_map(&map);
|
||||||
|
rounds += 1;
|
||||||
|
let mut check_next = HashSet::new();
|
||||||
|
|
||||||
|
for pos in to_check {
|
||||||
|
let mut reachable = vec![pos];
|
||||||
|
if pos.0 != 0 {
|
||||||
|
reachable.push((pos.0 - 1, pos.1));
|
||||||
|
}
|
||||||
|
if pos.0 != height - 1 {
|
||||||
|
reachable.push((pos.0 + 1, pos.1));
|
||||||
|
}
|
||||||
|
if pos.1 != 0 {
|
||||||
|
reachable.push((pos.0, pos.1 - 1));
|
||||||
|
}
|
||||||
|
if pos.1 != width - 1 {
|
||||||
|
reachable.push((pos.0, pos.1 + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
for p in reachable {
|
||||||
|
if p == *target {
|
||||||
|
return (rounds, map);
|
||||||
|
}
|
||||||
|
if matches!(map[p.0][p.1], Tile::Empty) {
|
||||||
|
check_next.insert(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_check = check_next;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/day25.rs
Normal file
83
src/day25.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub fn parse_snafus(input: &str) -> Vec<i64> {
|
||||||
|
input.lines().map(snafu_to_num).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snafu_to_num(snafu: &str) -> i64 {
|
||||||
|
let mut pow = 1;
|
||||||
|
let mut result = 0;
|
||||||
|
for c in snafu.chars().rev() {
|
||||||
|
let digit = match c {
|
||||||
|
'=' => -2,
|
||||||
|
'-' => -1,
|
||||||
|
'0' => 0,
|
||||||
|
'1' => 1,
|
||||||
|
'2' => 2,
|
||||||
|
_ => panic!("Unknown digit {}", c),
|
||||||
|
};
|
||||||
|
result += pow * digit;
|
||||||
|
pow *= 5;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_to_snafu(num: i64) -> String {
|
||||||
|
assert!(num > 0);
|
||||||
|
|
||||||
|
let exp = (num as f64).log(5.0).floor() as u32 + 1;
|
||||||
|
let mut result = VecDeque::with_capacity((exp as usize) + 1);
|
||||||
|
let mut pow = 5_i64.pow(exp);
|
||||||
|
let mut rem = num;
|
||||||
|
|
||||||
|
while pow >= 1 {
|
||||||
|
let digit = rem / pow;
|
||||||
|
rem %= pow;
|
||||||
|
result.push_back(digit);
|
||||||
|
pow /= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in (0..result.len()).rev() {
|
||||||
|
if result[i] > 2 {
|
||||||
|
result[i] -= 5;
|
||||||
|
result[i - 1] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
.iter()
|
||||||
|
.skip_while(|d| **d == 0)
|
||||||
|
.map(|d| match d {
|
||||||
|
-2 => '=',
|
||||||
|
-1 => '-',
|
||||||
|
0 => '0',
|
||||||
|
1 => '1',
|
||||||
|
2 => '2',
|
||||||
|
_ => panic!("Unknown digit {}", d),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn num_to_snafu_examples() {
|
||||||
|
use crate::day25::num_to_snafu;
|
||||||
|
assert_eq!(num_to_snafu(1), "1");
|
||||||
|
assert_eq!(num_to_snafu(2), "2");
|
||||||
|
assert_eq!(num_to_snafu(3), "1=");
|
||||||
|
assert_eq!(num_to_snafu(4), "1-");
|
||||||
|
assert_eq!(num_to_snafu(5), "10");
|
||||||
|
assert_eq!(num_to_snafu(6), "11");
|
||||||
|
assert_eq!(num_to_snafu(7), "12");
|
||||||
|
assert_eq!(num_to_snafu(8), "2=");
|
||||||
|
assert_eq!(num_to_snafu(9), "2-");
|
||||||
|
assert_eq!(num_to_snafu(10), "20");
|
||||||
|
assert_eq!(num_to_snafu(15), "1=0");
|
||||||
|
assert_eq!(num_to_snafu(20), "1-0");
|
||||||
|
assert_eq!(num_to_snafu(2022), "1=11-2");
|
||||||
|
assert_eq!(num_to_snafu(12345), "1-0---0");
|
||||||
|
assert_eq!(num_to_snafu(314159265), "1121-1110-1=0");
|
||||||
|
assert_eq!(num_to_snafu(4890), "2=-1=0");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use crate::util::Dir;
|
||||||
pub enum Dir {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Motion = (Dir, usize);
|
pub type Motion = (Dir, usize);
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,15 @@ pub mod day14;
|
|||||||
pub mod day15;
|
pub mod day15;
|
||||||
pub mod day16;
|
pub mod day16;
|
||||||
pub mod day17;
|
pub mod day17;
|
||||||
|
pub mod day18;
|
||||||
|
pub mod day19;
|
||||||
pub mod day2;
|
pub mod day2;
|
||||||
|
pub mod day20;
|
||||||
|
pub mod day21;
|
||||||
|
pub mod day22;
|
||||||
|
pub mod day23;
|
||||||
|
pub mod day24;
|
||||||
|
pub mod day25;
|
||||||
pub mod day3;
|
pub mod day3;
|
||||||
pub mod day4;
|
pub mod day4;
|
||||||
pub mod day5;
|
pub mod day5;
|
||||||
|
|||||||
109
src/util.rs
109
src/util.rs
@ -1,5 +1,9 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use itertools::MinMaxResult;
|
||||||
|
|
||||||
pub fn parse_input() -> String {
|
pub fn parse_input() -> String {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
@ -73,3 +77,108 @@ impl Coordinate for SignedCoord {
|
|||||||
(self.0 as usize, self.1 as usize)
|
(self.0 as usize, self.1 as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait BnBState<T> {
|
||||||
|
fn finished(&self) -> bool;
|
||||||
|
fn lower_bound(&self, extra: &T) -> usize;
|
||||||
|
fn upper_bound(&self, extra: &T) -> usize;
|
||||||
|
fn possible_actions(&self, extra: &T) -> Vec<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maximize<E, S>(initial_state: &S, extra: &E) -> S
|
||||||
|
where
|
||||||
|
S: BnBState<E> + Clone + Hash + Eq,
|
||||||
|
{
|
||||||
|
let mut lower_bound = initial_state.lower_bound(extra);
|
||||||
|
let mut best = initial_state.clone();
|
||||||
|
|
||||||
|
let mut next = vec![(initial_state.clone(), initial_state.upper_bound(extra))];
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
visited.insert(initial_state.clone());
|
||||||
|
|
||||||
|
while let Some(n) = next.pop() {
|
||||||
|
if n.1 < lower_bound {
|
||||||
|
// Between pushing this state and popping it, we've found a better solution
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = n.0;
|
||||||
|
for action in state.possible_actions(extra) {
|
||||||
|
if action.finished() {
|
||||||
|
let action_lower = action.lower_bound(extra);
|
||||||
|
if action_lower > lower_bound {
|
||||||
|
lower_bound = action_lower;
|
||||||
|
best = action;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let action_upper = action.upper_bound(extra);
|
||||||
|
if action_upper > lower_bound && !visited.contains(&action) {
|
||||||
|
next.push((action.clone(), action_upper));
|
||||||
|
visited.insert(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
best
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Dir {
|
||||||
|
Right = 0,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Up,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dir {
|
||||||
|
pub fn from_usize(n: usize) -> Option<Dir> {
|
||||||
|
match n {
|
||||||
|
0 => Some(Dir::Right),
|
||||||
|
1 => Some(Dir::Down),
|
||||||
|
2 => Some(Dir::Left),
|
||||||
|
3 => Some(Dir::Up),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_char(c: char) -> Option<Dir> {
|
||||||
|
match c {
|
||||||
|
'^' => Some(Dir::Up),
|
||||||
|
'>' => Some(Dir::Right),
|
||||||
|
'v' => Some(Dir::Down),
|
||||||
|
'<' => Some(Dir::Left),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Dir::Right => ">",
|
||||||
|
Dir::Down => "v",
|
||||||
|
Dir::Left => "<",
|
||||||
|
Dir::Up => "^",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cw(&self) -> Dir {
|
||||||
|
let d = *self as usize;
|
||||||
|
Self::from_usize((d + 1) % 4).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ccw(&self) -> Dir {
|
||||||
|
let d = *self as usize;
|
||||||
|
// Add 3 instead of subtracting one to avoid negative numbers
|
||||||
|
Self::from_usize((d + 3) % 4).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn minmax_unwrap<T: Clone>(mm: &MinMaxResult<T>) -> (T, T) {
|
||||||
|
match mm {
|
||||||
|
MinMaxResult::NoElements => panic!("No elements"),
|
||||||
|
MinMaxResult::OneElement(a) => (a.clone(), a.clone()),
|
||||||
|
MinMaxResult::MinMax(a, b) => (a.clone(), b.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user