aoc-2021-rust

git clone https://git.sinitax.com/sinitax/aoc-2021-rust
Log | Files | Refs | README | sfeed.txt

commit 4c9b5e23a5330203177f7a5902396859f5cfe4db
parent ea68649cfcea407905507dfbe57a06b86a20e17a
Author: Louis Burda <quent.burda@gmail.com>
Date:   Wed, 19 Apr 2023 04:29:36 +0200

[Day 23] Add cost heuristic (A*)

Diffstat:
Msrc/.gitignore | 1+
Msrc/23/src/main.rs | 362++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/23/test2 | 4++--
Asrc/23/test3 | 9+++++++++
4 files changed, 223 insertions(+), 153 deletions(-)

diff --git a/src/.gitignore b/src/.gitignore @@ -3,3 +3,4 @@ Cargo.lock template compile_commands.json main +perf.* diff --git a/src/23/src/main.rs b/src/23/src/main.rs @@ -1,21 +1,9 @@ use priority_queue::PriorityQueue; -use std::collections::HashMap; - -macro_rules! on_slot { - ( $x:expr ) => { - $x >= SLOTS_BEGIN && $x < SLOTS_END && (($x - SLOTS_BEGIN) % 2 == 0) - } -} - -//macro_rules! slot2idx { -// ( $x:expr ) => { -// (($x - SLOTS_BEGIN) / 2) as usize -// } -//} +use std::{collections::HashMap, hash::Hash}; macro_rules! idx2slot { - ( $x:expr ) => { - SLOTS_BEGIN + ($x * 2) as isize + ( $cfg:expr, $t:expr ) => { + $cfg.slot_x.min + ($t * 2) as isize } } @@ -26,12 +14,34 @@ struct Pos { } #[derive(PartialEq,Eq,Hash,Clone,Copy)] +struct DJKAgentState { + moved_out: bool, + moved_in: bool, + pos: Pos +} + +#[derive(PartialEq,Eq,Hash,Clone)] struct DJKState { - moved_out: [bool; 8], - moved_in: [bool; 8], - pos: [Pos; 8] + agents: [Vec<DJKAgentState>; 4] +} + +#[derive(Clone)] +struct Range { + min: isize, + max: isize } +#[derive(Clone)] +struct MapConfig { + hallway_y: isize, + hallway: Range, + slot_x: Range, + slot_y: Range, +} + +static AGENT_SYMBS: [char; 4] = ['A', 'B', 'C', 'D']; +static MOVE_COST: [isize; 4] = [1, 10, 100, 1000]; + impl Pos { fn dist_to(&self, other: &Self) -> isize { return isize::abs(self.x - other.x) @@ -50,10 +60,12 @@ impl DJKState { let mut nc = ' '; if c.is_some() { nc = if *c.unwrap() == '#' { '#' } else { ' ' }; - for i in 0..8 { - if p == self.pos[i] { - nc = SYMB[i/2]; - break; + for t in 0..4 { + for agent in &self.agents[t] { + if p == agent.pos { + nc = AGENT_SYMBS[t]; + break; + } } } } @@ -64,25 +76,10 @@ impl DJKState { } } -static SLOTS_BEGIN: isize = 3; -static SLOTS_END: isize = 10; -static SLOTS: [isize; 4] = [3, 5, 7, 9]; - -static SPOTS_BEGIN: isize = 1; -static SPOTS_END: isize = 12; -//static SPOTS: [isize; 7] = [1, 2, 4, 6, 8, 10, 11]; - -static HALLWAY: isize = 1; -static SLOT_UPPER: isize = 2; -static SLOT_LOWER: isize = 3; - -static SYMB: [char; 4] = ['A', 'B', 'C', 'D']; - -static MOVE_COST: [isize; 4] = [1, 10, 100, 1000]; - -fn parse_input(input: &str) -> HashMap<Pos,char> { +fn parse_input(input: &str) -> (HashMap<Pos,char>, MapConfig) { let mut map: HashMap<Pos,char> = HashMap::new(); - let mut y: usize = 0; + + let mut y: isize = 0; for line in input.lines() { if line.is_empty() { continue; } for (x,c) in line.chars().enumerate() { @@ -91,173 +88,236 @@ fn parse_input(input: &str) -> HashMap<Pos,char> { } y += 1; } - return map; -} -fn find_pos(map: &HashMap<Pos,char>, sc: char) -> [Pos; 2] { - let mut agents: Vec<Pos> = Vec::new(); - for (p,c) in map.iter() { - if *c == sc { - agents.push(p.clone()); - } - } - return agents.try_into().unwrap_or_else(|_| panic!()); -} + let cfg = MapConfig { + hallway_y: 1, + hallway: Range { min: 1, max: 12 }, + slot_x: Range { min: 3, max: 10 }, + slot_y: Range { min: 1, max: y - 1 } + }; -fn in_slot(state: &DJKState, agent: usize) -> bool { - if state.pos[agent].x == SLOTS[agent / 2] { - if state.pos[agent].y == SLOT_LOWER { - return true; - } + return (map, cfg); +} - if state.pos[agent].y == SLOT_UPPER { - let other = if agent % 2 == 0 { agent + 1 } else { agent - 1 }; - return state.pos[other].x == SLOTS[other / 2] - && state.pos[other].y == SLOT_LOWER; +fn heuristic(cfg: &MapConfig, state: &DJKState) -> isize { + let mut cost: isize = 0; + for t in 0..4 { + for agent in &state.agents[t] { + let slot_x = idx2slot!(cfg, t); + if agent.pos.x != slot_x { + cost += agent.pos.dist_to(&Pos { x: slot_x, y: cfg.hallway_y }); + } } } - return false; + return cost; } -fn try_move_out(map: &HashMap<Pos,char>, +fn try_move_out(map: &HashMap<Pos,char>, cfg: &MapConfig, + costmap: &mut HashMap<DJKState,isize>, active: &mut PriorityQueue<DJKState,isize>, - state: &DJKState, cost: isize, agent: usize) { - let mut minx: isize = SPOTS_BEGIN; - let mut maxx: isize = SPOTS_END; - for i in 0..8 { - if i == agent { continue; } - - if state.pos[i].x == state.pos[agent].x - && state.pos[i].y < state.pos[agent].y { return; } - - if state.pos[i].y == HALLWAY { - if state.pos[i].x > state.pos[agent].x { - maxx = isize::min(maxx, state.pos[i].x); - } else { - minx = isize::max(minx, state.pos[i].x+1); + state: &DJKState, cost: isize, at: usize, ai: usize) { + let cur = &state.agents[at][ai]; + + let mut minx: isize = cfg.hallway.min; + let mut maxx: isize = cfg.hallway.max; + for t in 0..4 { + for (i,agent) in state.agents[t].iter().enumerate() { + if t == at && i == ai { continue; } + + if agent.pos.x == cur.pos.x && agent.pos.y < cur.pos.y { return; } + + if agent.pos.y == cfg.hallway_y { + if agent.pos.x > cur.pos.x { + maxx = isize::min(maxx, agent.pos.x); + } else { + minx = isize::max(minx, agent.pos.x + 1); + } } } } for x in minx..maxx { /* skip spots over slots */ - if on_slot!(x) { continue; } + if x >= cfg.slot_x.min && x < cfg.slot_x.max + && ((x - cfg.slot_x.min) % 2 == 0) { continue; } let mut nstate = state.clone(); - nstate.pos[agent].x = x; - nstate.pos[agent].y = HALLWAY; - nstate.moved_out[agent] = true; - let ncost = cost + MOVE_COST[agent / 2] - * state.pos[agent].dist_to(&nstate.pos[agent]); - nstate.print(&map); - active.push_increase(nstate, -ncost); - aoc::debugln!("MOVE OUT {} {}", ncost, active.len()); + nstate.agents[at][ai].pos.x = x; + nstate.agents[at][ai].pos.y = cfg.hallway_y; + nstate.agents[at][ai].moved_out = true; + let ncost_g = cost + MOVE_COST[at] + * state.agents[at][ai].pos.dist_to(&nstate.agents[at][ai].pos); + let ncost_f = ncost_g + heuristic(cfg, &nstate); + + let res = costmap.get_mut(&nstate); + if res.is_none() || *res.unwrap() > ncost_g { + if aoc::get_debug() > 1 { + aoc::debugln!("MOVE OUT {} {}", ncost_g, ncost_f); + nstate.print(&map); + aoc::debugln!(""); + } + + costmap.insert(nstate.clone(), ncost_g); + active.push_increase(nstate, -ncost_f); + } } } -fn try_move_in(map: &HashMap<Pos,char>, +fn try_move_in(map: &HashMap<Pos,char>, cfg: &MapConfig, + costmap: &mut HashMap<DJKState,isize>, active: &mut PriorityQueue<DJKState,isize>, - state: &DJKState, cost: isize, agent: usize) { - let mut lower_taken = false; - for i in 0..8 { - if i == agent { continue; } - - if state.pos[i].y == HALLWAY { - /* another agent is in the way */ - if state.pos[i].x > state.pos[agent].x { - if idx2slot!(agent / 2) > state.pos[i].x { return; } - } else { - if idx2slot!(agent / 2) < state.pos[i].x { return; } + state: &DJKState, cost: isize, at: usize, ai: usize) { + + let mut slot_free_y = cfg.slot_y.max-1; + for t in 0..4 { + for (i,agent) in state.agents[t].iter().enumerate() { + if t == at && i == ai { continue; } + + if agent.pos.y == cfg.hallway_y { + /* another agent is in the way */ + if agent.pos.x > state.agents[at][ai].pos.x { + if idx2slot!(cfg, at) > agent.pos.x { return; } + } else { + if idx2slot!(cfg, at) < agent.pos.x { return; } + } + } else if agent.pos.x == idx2slot!(cfg, at) { + slot_free_y = isize::min(slot_free_y, agent.pos.y - 1); + if slot_free_y == cfg.hallway_y { return; } } - } else if state.pos[i].x == idx2slot!(agent / 2) { - /* upper slot taken */ - if state.pos[i].y == SLOT_UPPER { return; } - - /* agent of other type in slot */ - if i / 2 != agent / 2 { return } - - lower_taken = true; } } - let y = if lower_taken { SLOT_UPPER } else { SLOT_LOWER }; let mut nstate = state.clone(); - nstate.pos[agent].x = idx2slot!(agent / 2); - nstate.pos[agent].y = y; - nstate.moved_in[agent] = true; - let ncost = cost + MOVE_COST[agent / 2] - * state.pos[agent].dist_to(&nstate.pos[agent]); - nstate.print(map); - active.push_increase(nstate, -ncost); - aoc::debugln!("MOVE IN {} {}", ncost, active.len()); + nstate.agents[at][ai].pos.x = idx2slot!(cfg, at); + nstate.agents[at][ai].pos.y = slot_free_y; + nstate.agents[at][ai].moved_in = true; + let ncost_g = cost + MOVE_COST[at] + * state.agents[at][ai].pos.dist_to(&nstate.agents[at][ai].pos); + let ncost_f = ncost_g + heuristic(cfg, &nstate); + + let res = costmap.get_mut(&nstate); + if res.is_none() || *res.unwrap() > ncost_g { + if aoc::get_debug() > 1 { + aoc::debugln!("MOVE IN {} {}", ncost_g, ncost_f); + nstate.print(map); + aoc::debugln!(""); + } + + costmap.insert(nstate.clone(), ncost_g); + active.push_increase(nstate, -ncost_f); + } } -fn try_move(map: &HashMap<Pos,char>, +fn try_move(map: &HashMap<Pos,char>, cfg: &MapConfig, + costmap: &mut HashMap<DJKState,isize>, active: &mut PriorityQueue<DJKState,isize>, - state: &DJKState, cost: isize, agent: usize) -> bool { - if !state.moved_out[agent] { - try_move_out(map, active, state, cost, agent); + state: &DJKState, cost: isize, at: usize, ai: usize) -> bool { + if !state.agents[at][ai].moved_out { + try_move_out(map, cfg, costmap, active, state, cost, at, ai); return false; - } else if !state.moved_in[agent] { - try_move_in(map, active, state, cost, agent); + } else if !state.agents[at][ai].moved_in { + try_move_in(map, cfg, costmap, active, state, cost, at, ai); return false; } else { - return state.moved_in.iter().all(|b| *b); + return state.agents.iter().all(|v| v.iter().all(|a| a.moved_in)); } } -fn part1(aoc: &mut aoc::Info) { - let map = parse_input(&aoc.input); - +fn organize(map: &HashMap<Pos,char>, cfg: &MapConfig) -> Option<usize> { let mut active: PriorityQueue<DJKState,isize> = PriorityQueue::new(); + let mut costmap: HashMap<DJKState,isize> = HashMap::new(); - let mut initial = DJKState { - moved_out: [false; 8], moved_in: [false; 8], - pos: [find_pos(&map, 'A')[0], find_pos(&map, 'A')[1], - find_pos(&map, 'B')[0], find_pos(&map, 'B')[1], - find_pos(&map, 'C')[0], find_pos(&map, 'C')[1], - find_pos(&map, 'D')[0], find_pos(&map, 'D')[1]] - }; - for i in 0..8 { - if in_slot(&initial, i) { - initial.moved_in[i] = true; - initial.moved_out[i] = true; + let mut initial = DJKState { agents: [vec![], vec![], vec![], vec![]] }; + for (p,c) in map.iter() { + let idx = AGENT_SYMBS.iter().position(|sc| sc == c); + if idx.is_some() { + let mut agent = DJKAgentState { + moved_out: false, + moved_in: false, + pos: p.clone() + }; + if p.x == cfg.slot_x.min + 2 * idx.unwrap() as isize { + let fixed = (p.y+1..cfg.slot_y.max) + .all(|y| map.get(&Pos { x: p.x, y }).unwrap() == c); + agent.moved_out = fixed; + agent.moved_in = fixed; + } + initial.agents[idx.unwrap()].push(agent); } } - active.push(initial, 0); + active.push(initial.clone(), -heuristic(&cfg, &initial)); + costmap.insert(initial, 0); - let mut answer: Option<usize> = None; let mut advanced: usize = 0; - while !active.is_empty() && answer.is_none() { - let (state, mut cost) = active.pop().unwrap(); - cost = -cost; /* inverted for correct priority */ + while !active.is_empty() { + let (state, _) = active.pop().unwrap(); + let cost_g = *costmap.get(&state).unwrap(); advanced += 1; - aoc::debugln!("-----------------"); - aoc::debugln!("{}", cost); - aoc::debugln!("LEN {}", active.len()); if aoc::get_debug() > 1 { + aoc::debugln!("-----------------"); + aoc::debugln!("LEN {}", active.len()); state.print(&map); aoc::debugln!(""); } - - for i in 0..8 { - let done = try_move(&map, &mut active, &state, cost, i); - if done { - aoc::debugln!("ANSWER?"); - answer = Some(cost as usize); - break; + aoc::debugln!("{}", cost_g); + + for t in 0..4 { + for i in 0..state.agents[t].len() { + let done = try_move(&map, &cfg, &mut costmap, + &mut active, &state, cost_g, t, i); + if done { + aoc::debugln!("ANSWER?"); + return Some(cost_g as usize); + } } } } aoc::debugln!("Advanced {} states", advanced); + return None; +} + +fn extend_map(s_map: &HashMap<Pos,char>, s_cfg: &MapConfig) + -> (HashMap<Pos,char>, MapConfig) { + let insert: [&str; 2] = [" #D#C#B#A# ", " #D#B#A#C# "]; + + let mut map: HashMap<Pos,char> = HashMap::new(); + for (y,l) in insert.iter().enumerate() { + for (x,c) in l.chars().enumerate() { + let p = Pos { x: x as isize, y: s_cfg.hallway_y + 1 + (y as isize) }; + map.insert(p, c); + } + } + for (p,&c) in s_map.iter() { + if p.x >= s_cfg.slot_x.min - 1 && p.x < s_cfg.slot_x.max + 1 + && p.y >= s_cfg.hallway_y + 1 { + map.insert(Pos { x: p.x, y: p.y + 2 }, c); + } else { + map.insert(p.clone(), c); + } + } + + let mut cfg = s_cfg.clone(); + cfg.slot_y.max += 2; + + return (map, cfg); +} + +fn part1(aoc: &mut aoc::Info) { + let (map,cfg) = parse_input(&aoc.input); + let answer = organize(&map, &cfg); aoc.answer = Some(format!("{}", answer.unwrap())); + aoc.solution = Some("15385"); } -fn part2(_aoc: &mut aoc::Info) { +fn part2(aoc: &mut aoc::Info) { + let (s_map,s_cfg) = parse_input(&aoc.input); + let (map,cfg) = extend_map(&s_map, &s_cfg); + let answer = organize(&map, &cfg); + aoc.answer = Some(format!("{}", answer.unwrap())); } fn main() { diff --git a/src/23/test2 b/src/23/test2 @@ -1,6 +1,6 @@ ############# #...........# -###B#A#C#D### - #A#B#C#D# +###.#C#B#D### + #A#B#A#D# ######### diff --git a/src/23/test3 b/src/23/test3 @@ -0,0 +1,9 @@ + + +############# +#...........# +###.#C#B#D### + #A#B#A#D# + #A#B#C#D# + #A#B#C#D# + #########