day18: problem 2 solution

This commit is contained in:
Keenan Tims 2023-12-18 01:54:31 -08:00
parent 0a9fa8e32f
commit 3bc073f9b8
Signed by: ktims
GPG Key ID: 11230674D69038D4

View File

@ -37,6 +37,14 @@ enum Direction {
Down, Down,
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
enum Turn {
LeftNinety,
RightNinety,
OneEighty,
None,
}
impl Direction { impl Direction {
const fn all() -> &'static [Self; 4] { const fn all() -> &'static [Self; 4] {
&[ &[
@ -62,6 +70,25 @@ impl Direction {
Direction::Down => (0, 1), Direction::Down => (0, 1),
} }
} }
fn turn_kind(&self, next_dir: Direction) -> Turn {
if *self == next_dir {
Turn::None
} else if self.opposite() == next_dir {
Turn::OneEighty
} else {
match self {
Direction::Left if next_dir == Direction::Up => Turn::RightNinety,
Direction::Left if next_dir == Direction::Down => Turn::LeftNinety,
Direction::Right if next_dir == Direction::Up => Turn::LeftNinety,
Direction::Right if next_dir == Direction::Down => Turn::RightNinety,
Direction::Up if next_dir == Direction::Left => Turn::LeftNinety,
Direction::Up if next_dir == Direction::Right => Turn::RightNinety,
Direction::Down if next_dir == Direction::Right => Turn::LeftNinety,
Direction::Down if next_dir == Direction::Left => Turn::RightNinety,
_ => unreachable!(),
}
}
}
} }
impl From<&str> for Direction { impl From<&str> for Direction {
@ -100,7 +127,13 @@ impl From<&str> for DigInstruction {
let (dir, count, color) = ( let (dir, count, color) = (
parts.next().unwrap(), parts.next().unwrap(),
parts.next().unwrap(), parts.next().unwrap(),
parts.next().unwrap(), parts
.next()
.unwrap()
.chars()
.skip(2)
.take(6)
.collect::<String>(),
); );
Self { Self {
dir: dir.into(), dir: dir.into(),
@ -110,24 +143,42 @@ impl From<&str> for DigInstruction {
} }
} }
impl DigInstruction {
fn part2_transform(&mut self) {
let (distance_s, direction_s) = self.color.split_at(5);
self.count = usize::from_str_radix(distance_s, 16).unwrap();
self.dir = match direction_s {
"0" => Direction::Right,
"1" => Direction::Down,
"2" => Direction::Left,
"3" => Direction::Up,
s => panic!("`{}` is not a valid direction code", s)
};
}
}
#[derive(Debug)] #[derive(Debug)]
struct DigPlan { struct DigPlan {
instructions: Vec<DigInstruction>, instructions: Vec<DigInstruction>,
} }
impl DigPlan {
fn part2_transform(&mut self) {
for i in &mut self.instructions {
i.part2_transform();
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DigTile { struct DigTile {
position: Position, position: Position,
depth: usize,
color: String,
} }
impl Default for DigTile { impl Default for DigTile {
fn default() -> Self { fn default() -> Self {
Self { Self {
position: (0, 0), position: (0, 0),
depth: 0,
color: "000000".into(),
} }
} }
} }
@ -140,6 +191,12 @@ struct DigHole {
tiles_map: HashMap<Position, DigTile>, tiles_map: HashMap<Position, DigTile>,
x_range: Range<isize>, x_range: Range<isize>,
y_range: Range<isize>, y_range: Range<isize>,
area: u64,
}
// determinant of positions p1 and p2
fn det(p1: Position, p2: Position) -> i64 {
((p1.0 * p2.1) - (p1.1 * p2.0)) as i64
} }
impl DigHole { impl DigHole {
@ -149,88 +206,63 @@ impl DigHole {
tiles_map: HashMap::new(), tiles_map: HashMap::new(),
x_range: 0..0, x_range: 0..0,
y_range: 0..0, y_range: 0..0,
area: 0,
} }
} }
fn pos_offset(&self, pos: Position, offset: (isize, isize)) -> Position { fn pos_offset_n(&self, pos: Position, offset: (isize, isize), n: usize) -> Position {
(pos.0 + offset.0, pos.1 + offset.1) (pos.0 + offset.0 * n as isize, pos.1 + offset.1 * n as isize)
} }
fn run_plan(&mut self, plan: &DigPlan) { fn run_plan(&mut self, plan: &DigPlan) {
// start position is 1m deep
let mut cur_pos = (0, 0); let mut cur_pos = (0, 0);
let (mut max_x, mut max_y) = (0, 0);
self.tiles_loop.push_back(DigTile { self.tiles_loop.push_back(DigTile {
position: cur_pos, position: cur_pos,
depth: 1,
color: "000000".into(),
}); });
for i in &plan.instructions { let mut move_offset;
println!("instruction: {:?}", i); for (idx, i) in plan.instructions.iter().enumerate() {
for _ in 0..i.count { let prev_instruction = if idx > 0 {
println!(" cur_pos: {:?}", cur_pos); &plan.instructions[idx - 1]
cur_pos = (cur_pos.0 + i.dir.offset().0, cur_pos.1 + i.dir.offset().1);
(max_x, max_y) = (
std::cmp::max(cur_pos.0, max_x),
std::cmp::max(cur_pos.1, max_y),
);
self.tiles_loop.push_back(DigTile {
position: cur_pos,
depth: 1,
color: i.color.to_owned(),
});
}
}
// 2d vec dimension is (max_x+1, max_y+1)
for tile in &self.tiles_loop {
self.x_range.start = std::cmp::min(self.x_range.start, tile.position.0);
self.x_range.end = std::cmp::max(self.x_range.end, tile.position.0 + 1);
self.y_range.start = std::cmp::min(self.y_range.start, tile.position.1);
self.y_range.end = std::cmp::max(self.y_range.end, tile.position.1 + 1);
self.tiles_map.insert(tile.position, tile.clone());
}
println!("{}", self);
self.fill_at(self.find_first_inside().unwrap());
}
fn fill_at(&mut self, pos: Position) {
println!("filling at {:?}", pos);
let mut to_fill = vec![pos];
while let Some(fill_pos) = to_fill.pop() {
if self.tiles_map.contains_key(&fill_pos) {
continue;
} else { } else {
self.tiles_map.insert( &plan.instructions[plan.instructions.len() - 1]
fill_pos, };
DigTile { let Some(next_instruction) = plan
position: fill_pos, .instructions
depth: 1, .get(idx + 1)
color: "000000".to_owned(), .or(Some(&plan.instructions[0]))
}, else {
); panic!()
} };
for new_pos in Direction::all() let cur_turn = prev_instruction.dir.turn_kind(i.dir);
.iter() let next_turn = i.dir.turn_kind(next_instruction.dir);
.map(|dir| self.pos_offset(fill_pos, dir.offset())) // point needs to live on the 'outside' corner of the character. to
.filter(|pos| !self.tiles_map.contains_key(&pos)) // achieve this we need to offset the move by the following
{ move_offset = match (cur_turn, next_turn) {
to_fill.push(new_pos); (Turn::RightNinety, Turn::RightNinety) => 1,
} (Turn::RightNinety, Turn::LeftNinety) => 0,
(Turn::LeftNinety, Turn::LeftNinety) => -1,
(Turn::LeftNinety, Turn::RightNinety) => 0,
t => panic!("turn {:?} not allowed here", t),
};
cur_pos = self.pos_offset_n(
cur_pos,
i.dir.offset(),
(i.count as isize + move_offset) as usize,
);
self.tiles_loop.push_back(DigTile {
position: cur_pos,
});
} }
}
fn find_first_inside(&self) -> Option<Position> { // Shoelace formula
for y in self.y_range.clone() { // https://en.wikipedia.org/wiki/Shoelace_formula
let mut inside = false; let double_area: i64 = self
for x in self.x_range.clone() { .tiles_loop
if self.tiles_map.contains_key(&(x, y)) && (!self.tiles_map.contains_key(&(x - 1, y)) || !self.tiles_map.contains_key(&(x+1, y))) .iter()
{ .tuple_windows()
inside = !inside; .map(|(a, b)| det(a.position, b.position))
} .sum();
if inside && !self.tiles_map.contains_key(&(x, y)) { self.area = (double_area / 2).abs() as u64;
return Some((x as isize, y as isize));
};
}
}
None
} }
} }
@ -266,16 +298,18 @@ impl<T: BufRead> From<Lines<T>> for DigPlan {
fn problem1<T: BufRead>(input: Lines<T>) -> u64 { fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let plan = DigPlan::from(input); let plan = DigPlan::from(input);
println!("{:?}", plan);
let mut dig = DigHole::new(); let mut dig = DigHole::new();
dig.run_plan(&plan); dig.run_plan(&plan);
println!("{}", dig); dig.area
dig.tiles_map.iter().count() as u64
} }
// PROBLEM 2 solution // PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 { fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0 let mut plan = DigPlan::from(input);
let mut dig = DigHole::new();
plan.part2_transform();
dig.run_plan(&plan);
dig.area
} }
#[cfg(test)] #[cfg(test)]
@ -298,6 +332,17 @@ U 3 (#a77fa3)
L 2 (#015232) L 2 (#015232)
U 2 (#7a21e3)"; U 2 (#7a21e3)";
const AREA16_SQUARE: &str = &"R 3 (#000000)
D 3 (#000000)
L 3 (#000000)
U 4 (#000000";
#[test]
fn area16_square() {
let c = Cursor::new(AREA16_SQUARE);
assert_eq!(problem1(c.lines()), 16);
}
#[test] #[test]
fn problem1_example() { fn problem1_example() {
let c = Cursor::new(EXAMPLE); let c = Cursor::new(EXAMPLE);
@ -307,6 +352,6 @@ U 2 (#7a21e3)";
#[test] #[test]
fn problem2_example() { fn problem2_example() {
let c = Cursor::new(EXAMPLE); let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0); assert_eq!(problem2(c.lines()), 952408144115);
} }
} }