diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d3d4b22..c646087 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -113,3 +113,8 @@ day-22: stage: build script: - cd day22; cargo run --release ./input + +day-23: + stage: build + script: + - cd day23; cargo run --release ./input diff --git a/day23/Cargo.toml b/day23/Cargo.toml new file mode 100644 index 0000000..3e1aa7d --- /dev/null +++ b/day23/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day23" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/day23/input b/day23/input new file mode 100644 index 0000000..64ceab9 --- /dev/null +++ b/day23/input @@ -0,0 +1,5 @@ +############# +#...........# +###A#D#B#D### + #B#C#A#C# + ######### diff --git a/day23/src/main.rs b/day23/src/main.rs new file mode 100644 index 0000000..5ad88d0 --- /dev/null +++ b/day23/src/main.rs @@ -0,0 +1,215 @@ +use std::{fs, env, collections::{HashSet, HashMap}}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +enum Amphipod { + Amber, + Bronze, + Copper, + Desert, + Placeholder +} + +impl Amphipod { + fn get_target_room(&self) -> usize { + return match self { + Amphipod::Amber => { 0 } + Amphipod::Bronze => { 1 } + Amphipod::Copper => { 2 } + Amphipod::Desert => { 3 } + _ => { panic!("Amphipod has no target room.") } + }; + } + + fn get_from_str(s: &str) -> Amphipod { + return match s { + "A" => { Amphipod::Amber } + "B" => { Amphipod::Bronze } + "C" => { Amphipod::Copper } + "D" => { Amphipod::Desert } + _ => { panic!("Amphipod not recognized.") } + }; + } +} + +type Action = ((usize, bool), (usize, bool), u32); + +fn read_input(path: &str) -> String { + return fs::read_to_string(path).expect("Cannot read file."); +} + +fn parse_input(s: &str) -> Vec> { + let l = s.lines().skip(2) + .map(|l| l.trim().split("#").filter(|e| !e.is_empty()) + .collect::>()) + .filter(|e| !e.is_empty()) + .collect::>>(); + let mut g = vec![ + vec![Amphipod::Placeholder; l.len()]; + l.iter().fold(0, |acc, e| std::cmp::max(e.len(), acc))]; + for (n, v) in l.iter().rev().enumerate() { + for (i, a) in v.iter().enumerate() { + g[i][n] = Amphipod::get_from_str(*a); + } + } + return g; +} + +fn expand_input(r: &mut Vec>) { + let mut s = vec![ + Amphipod::Amber, Amphipod::Copper, + Amphipod::Bronze, Amphipod::Amber, + Amphipod::Copper, Amphipod::Bronze, + Amphipod::Desert, Amphipod::Desert]; + for e in r.iter_mut() { + e.insert(1, s.pop().unwrap()); + e.insert(2, s.pop().unwrap()); + } +} + +fn no_need_to_move(r: &Vec>) -> HashSet<(usize, usize)> { + let mut h = HashSet::new(); + for (i, v) in r.iter().enumerate() { + for (l, a) in v.iter().enumerate() { + if a.get_target_room() == i && (l == 0 || h.contains(&(i, l - 1))) { + h.insert((i, l)); + } + } + } + return h; +} + +fn is_room_full(rooms: &Vec>, n: usize, s: &HashSet<(usize, usize)>, + room_depth: usize) -> bool { + return rooms[n].len() == room_depth + && s.iter().filter(|e| e.0 == n).count() == room_depth; +} + +fn is_target_room_ready(rooms: &Vec>, n: usize, s: &HashSet<(usize, usize)>, + room_depth: usize) -> bool { + return !is_room_full(rooms, n, s, room_depth) + && s.iter().filter(|e| e.0 == n).count() == rooms[n].len(); +} + +fn can_cross_hallway(s: usize, t: usize, h: &Vec>) -> bool { + if h.iter().all(|e| e.is_none()) { return true; } + if s == t { return false; } + let (b, e) = (std::cmp::min(s, t) + 1, std::cmp::max(s, t)); + return (b..e).all(|e| h[e].is_none()); +} + +fn room_to_hallway_pos(n: usize) -> usize { + return n * 2 + 2; +} + +fn get_move_cost(f: &(usize, bool), t: &(usize, bool), r: &Vec>, + d: usize, a: &Amphipod) -> u32 { + let s = if f.1 { room_to_hallway_pos(f.0) } else { f.0 } as i32; + let e = if t.1 { room_to_hallway_pos(t.0) } else { t.0 } as i32; + return ((s - e).abs() as u32 + + if f.1 { (d - r[f.0].len()) as u32 + 1 } else { 0 } + + if t.1 { (d - r[t.0].len()) as u32 } else { 0 }) + * 10u32.pow(a.get_target_room() as u32); +} + +fn get_next_moves(rs: &Vec>, d: usize, h: &Vec>, + s: &HashSet<(usize, usize)>) -> Vec{ + let f = (0..rs.len()).map(|e| room_to_hallway_pos(e)).collect::>(); + let mut a = vec![]; + for (i, e) in h.iter().enumerate() { + if e.is_none() { continue; } + let e = e.unwrap(); + let t = e.get_target_room(); + if is_target_room_ready(rs, t, s, d) + && can_cross_hallway(i, room_to_hallway_pos(t), h) { + return vec![(( + (i, false), + (t, true), + get_move_cost(&(i, false), &(t, true), rs, d, &e) + ))]; + } + } + for (n, r) in rs.iter().enumerate() { + if r.is_empty() { continue; } + let l = r.last().unwrap(); + let t = l.get_target_room(); + if l.get_target_room() != n + && is_target_room_ready(rs, t, s, d) + && can_cross_hallway(room_to_hallway_pos(n), room_to_hallway_pos(t), h) { + return vec![(( + (n, true), + (t, true), + get_move_cost(&(n, true), &(t, true), rs, d, &l)) + )]; + } + if !is_room_full(rs, n, s, d) && !is_target_room_ready(rs, n, s, d) { + for (j, o) in h.iter().enumerate() { + if o.is_some() || f.contains(&j) { continue; } + if can_cross_hallway(room_to_hallway_pos(n), j, h) { + a.push(( + (n, true), + (j, false), + get_move_cost(&(n, true), &(j, false), rs, d, &l))); + } + } + } + } + return a; +} + +fn apply_action(a: &Action, r: &mut Vec>, s: &mut u32, + h: &mut Vec>) { + *s += a.2; + let o; + if a.0.1 { + o = r[a.0.0].pop().unwrap(); + } else { + o = h[a.0.0].unwrap(); + h[a.0.0] = None; + } + if a.1.1 { + r[a.1.0].push(o); + } else { + h[a.1.0] = Some(o); + } +} + +fn compute_sorting(mut r: Vec>, mut h: Vec>, d: usize, + mut s: u32, b: &mut u32, a: &Action, + c: &mut HashMap<(Vec>, Vec>), u32>) -> Option { + apply_action(a, &mut r, &mut s, &mut h); + if s >= *c.get(&(r.clone(), h.clone())).unwrap_or(&u32::MAX) { return None; } + c.insert((r.clone(), h.clone()), s); + let n = no_need_to_move(&r); + if n.len() == r.len() * d { + *b = s; + return Some(s); + } + let m = get_next_moves(&r, d, &h, &n); + if m.is_empty() { return None; } + let mut o = None; + for mo in m { + let x = compute_sorting(r.clone(), h.clone(), d, s, b, &mo, c); + if x.is_some() && (o.is_none() || x.unwrap() < o.unwrap()) { o = x; } + } + return o; +} + +fn main() { + let args: Vec = env::args().collect(); + for arg in args.iter().skip(1) { + let input = read_input(&arg); + let mut vec_in = parse_input(&input); + let (mut s1, mut s2) = (u32::MAX, u32::MAX); + let a = ((0, true), (0, true), 0); + let mut c = HashMap::new(); + let h = vec![None; 11]; + compute_sorting(vec_in.clone(), h.clone(), 2, 0, &mut s1, &a, &mut c); + expand_input(&mut vec_in); + c.clear(); + compute_sorting(vec_in, h, 4, 0, &mut s2, &a, &mut c); + println!("[{}]", &arg); + println!("\t[Part 1] => Answer is '{}'.", s1); + println!("\t[Part 2] => Answer is '{}'.", s2); + } +} +