Compare commits

...

10 Commits

Author SHA1 Message Date
62c866abf9 Day 22, part 2 2022-12-22 17:21:06 +01:00
ae143d4c57 Navigable trait 2022-12-22 15:00:15 +01:00
5e6870d092 Day 22, part 1 2022-12-22 12:57:38 +01:00
a0fa5256e9 Day 16, part 2 2022-12-21 21:44:12 +01:00
49368e7985 Track paths during distance calculation 2022-12-21 19:54:27 +01:00
95e4095f32 Day 16, part 1 solved 2022-12-21 19:33:17 +01:00
0279edb249 Generalized branch-and-bound implementation 2022-12-21 19:32:31 +01:00
47cb2fefa5 Day 21, part 2 2022-12-21 18:45:28 +01:00
ec5b46d793 Use trees crate 2022-12-21 18:00:23 +01:00
9b42768fdb Day 21, part 1 2022-12-21 17:32:59 +01:00
15 changed files with 1247 additions and 211 deletions

7
Cargo.lock generated
View File

@ -22,6 +22,7 @@ dependencies = [
"num",
"once_cell",
"regex",
"trees",
]
[[package]]
@ -340,6 +341,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "trees"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de5f738ceab88e2491a94ddc33c3feeadfa95fedc60363ef110845df12f3878"
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -13,6 +13,7 @@ env_logger = "^0.10"
num = "^0.1"
lazy_static = "^1.4"
once_cell = "^1.16"
trees = "^0.4"
[[bin]]
name = "d1p1"

View File

@ -1,73 +1,19 @@
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
thread,
use aoc22::{
day16,
util::{self, BnBState},
};
use aoc22::{day16, util};
const TOTAL_TIME: usize = 30;
pub fn main() {
let valves = Arc::new(day16::parse_valves(&util::parse_input()));
let dists = Arc::new(day16::calc_dists(&valves));
let state = day16::State::new(&valves);
let valves = day16::parse_valves(&util::parse_input());
let paths = day16::calc_paths(&valves);
let state = day16::State::new(&valves, TOTAL_TIME, 1);
let possible_states = Arc::new(Mutex::new(Vec::new()));
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();
}
let state = util::maximize(&state, &(&valves, &paths));
println!(
"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
View 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/d21p1.rs Normal file
View 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
View 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
View 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
View 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);
}

View File

@ -1,14 +1,12 @@
use std::{
collections::{HashMap, HashSet, VecDeque},
hash::Hash,
sync::Mutex,
};
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
const TOTAL_TIME: usize = 30;
use crate::util;
#[derive(Debug, Clone)]
pub struct Valve {
@ -54,21 +52,40 @@ pub fn parse_valves(input: &String) -> Vec<Valve> {
.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 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 {
result[i][i] = 0;
result[i][i].dist = 0;
let mut next = VecDeque::new();
next.push_back(i);
while let Some(j) = next.pop_front() {
let d_next = result[i][j] + 1;
for j in &valves[j].tunnels {
let d_tunnel = result[i].get_mut(*j).unwrap();
if d_next < *d_tunnel {
*d_tunnel = d_next;
next.push_back(*j);
let d_next = result[i][j].dist + 1;
for tunnel in &valves[j].tunnels {
let d_tunnel = &result[i][*tunnel];
if d_next < d_tunnel.dist {
let mut path = result[i][j].path.clone();
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
}
#[derive(Debug, Clone, Hash, PartialEq)]
pub enum Goal {
Idle,
NothingReachable,
Opening(usize),
}
#[derive(Debug, Clone)]
pub struct State {
pub opened: u64,
pub location: usize,
pub locations: Vec<usize>,
pub goals: Vec<Goal>,
pub flow_rate: usize,
pub time_remaining: usize,
pub pressure_released: usize,
pub history: Vec<Vec<usize>>,
interesting: HashSet<usize>,
}
impl State {
pub fn new(valves: &Vec<Valve>) -> State {
pub fn new(valves: &Vec<Valve>, initial_time: usize, agents: usize) -> State {
let interesting = valves
.iter()
.enumerate()
@ -97,78 +123,244 @@ impl State {
.collect();
State {
opened: 0,
location: 0, // "AA" has to be the first valve alphabetically
locations: vec![0; agents],
goals: vec![Goal::Idle; agents],
flow_rate: 0,
time_remaining: TOTAL_TIME,
time_remaining: initial_time,
pressure_released: 0,
interesting,
history: vec![vec![0]; agents],
}
}
pub fn finished(&self) -> bool {
self.time_remaining == 0 || self.interesting.is_empty()
fn find_goals(&self, (_, paths): &ExtraArgs) -> Vec<State> {
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
}
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;
for i in &self.interesting {
let i = i.clone();
if (self.opened & (1 << i)) != 0 {
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 {
let flow = valves[i].flow_rate;
additional_flow += flow * (self.time_remaining - dist - 1);
}
}
self.lower_bound() + 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
self.lower_bound(&(valves, paths)) + additional_flow
}
}
impl Hash for State {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.opened.hash(state);
self.location.hash(state);
self.locations.hash(state);
self.goals.hash(state);
self.time_remaining.hash(state);
self.pressure_released.hash(state);
}
@ -177,26 +369,11 @@ impl Hash for State {
impl PartialEq for State {
fn eq(&self, other: &Self) -> bool {
self.opened == other.opened
&& self.location == other.location
&& self.locations == other.locations
&& self.goals == other.goals
&& self.time_remaining == other.time_remaining
&& self.pressure_released == other.pressure_released
}
}
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()
}
}

View File

@ -1,7 +1,7 @@
use std::collections::HashSet;
use regex::Regex;
use crate::util::{self, BnBState};
#[derive(Debug, Clone)]
pub struct Blueprint {
pub ore_ore_cost: usize,
@ -77,29 +77,6 @@ impl State {
}
}
pub fn finished(&self) -> bool {
self.time_remaining == 0
}
pub 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 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
@ -180,16 +157,6 @@ impl State {
})
}
pub fn upper_bound(&self) -> 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() + ((self.time_remaining - 1) * self.time_remaining) / 2
}
pub fn lower_bound(&self) -> usize {
self.geodes + self.geode_robots * self.time_remaining
}
fn run_steps(&mut self, n: usize) {
assert!(self.time_remaining >= n);
@ -201,38 +168,43 @@ impl State {
}
}
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);
let initial_upper = initial.upper_bound();
let mut next = Vec::new();
next.push((initial.clone(), initial_upper));
let mut visited = HashSet::new();
visited.insert(initial);
let mut lower_bound = 0;
while let Some(n) = next.pop() {
if n.1 < lower_bound {
// Between pushing this state and popping it, we've found a better lower bound
continue;
}
let state = n.0;
for action in state.possible_actions(blueprint) {
if action.finished() {
let action_lower = action.lower_bound();
if action_lower > lower_bound {
lower_bound = action_lower;
}
} else {
let action_upper = action.upper_bound();
if action_upper > lower_bound && !visited.contains(&action) {
next.push((action.clone(), action_upper));
visited.insert(action);
}
}
}
}
lower_bound
util::maximize(&initial, blueprint).lower_bound(blueprint)
}

149
src/day21.rs Normal file
View 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
View 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)
}

View File

@ -1,12 +1,6 @@
use std::collections::HashSet;
#[derive(Debug)]
pub enum Dir {
Left,
Right,
Up,
Down,
}
use crate::util::Dir;
pub type Motion = (Dir, usize);

View File

@ -11,6 +11,8 @@ pub mod day18;
pub mod day19;
pub mod day2;
pub mod day20;
pub mod day21;
pub mod day22;
pub mod day3;
pub mod day4;
pub mod day5;

View File

@ -1,5 +1,7 @@
use std::collections::HashSet;
use std::env;
use std::fs;
use std::hash::Hash;
pub fn parse_input() -> String {
let args: Vec<String> = env::args().collect();
@ -73,3 +75,81 @@ impl Coordinate for SignedCoord {
(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 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()
}
}