day18: problem 2 solution
This commit is contained in:
parent
0a9fa8e32f
commit
3bc073f9b8
207
18/src/main.rs
207
18/src/main.rs
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user