diff --git a/data/examples/12-4.txt b/data/examples/12-4.txt new file mode 100644 index 0000000..5818d30 --- /dev/null +++ b/data/examples/12-4.txt @@ -0,0 +1,5 @@ +EEEEE +EXXXX +EEEEE +EXXXX +EEEEE diff --git a/data/examples/12-5.txt b/data/examples/12-5.txt new file mode 100644 index 0000000..f7cd38f --- /dev/null +++ b/data/examples/12-5.txt @@ -0,0 +1,6 @@ +AAAAAA +AAABBA +AAABBA +ABBAAA +ABBAAA +AAAAAA diff --git a/src/bin/12.rs b/src/bin/12.rs index 4844484..a28abc1 100644 --- a/src/bin/12.rs +++ b/src/bin/12.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, fmt}; advent_of_code::solution!(12); +#[derive(PartialEq)] struct Plot { letter: char, region_id: Option, @@ -13,8 +14,20 @@ impl fmt::Debug for Plot { } } +type Direction = (isize, isize); +// impl fmt::Debug for Direction { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// if self == &(-1 as isize, 0 as isize) { write!(f, "UP") } +// else if self == &(1 as isize, 0 as isize) { write!(f, "DOWN") } +// else if self == &(0 as isize, -1 as isize) { write!(f, "LEFT") } +// else if self == &(0 as isize, 1 as isize) { write!(f, "RIGHT") } +// else { write!(f, "({}, {})", self.0, self.1) } +// } +// } + #[derive(Debug)] struct Region { + id: u32, edges: usize, count: usize } @@ -24,11 +37,11 @@ pub fn part_one(input: &str) -> Option { let mut regions: HashMap = HashMap::new(); let mut next_region_id = 1; - while let Some(plot) = get_next_unassigned_plot(&plots) { + while let Some(plot) = find_plot(&plots, |p| p.region_id == None) { plots[plot.0][plot.1].region_id = Some(next_region_id); let edge_count = count_edges(&plots, &plot); - regions.insert(next_region_id, Region { edges: edge_count, count: 1 }); + regions.insert(next_region_id, Region { id: next_region_id, edges: edge_count, count: 1 }); next_region_id += 1; update_neighbors(&mut plots, &plot, &mut regions); @@ -44,13 +57,40 @@ pub fn part_one(input: &str) -> Option { } pub fn part_two(input: &str) -> Option { - None + let mut plots = input.lines().map(|l| l.chars().map(|c| Plot { letter: c, region_id: None }).collect_vec()).collect_vec(); + let mut regions: HashMap = HashMap::new(); + let mut next_region_id = 1; + + while let Some(plot) = find_plot(&plots, |p| p.region_id == None) { + plots[plot.0][plot.1].region_id = Some(next_region_id); + + let edge_count = count_edges(&plots, &plot); + regions.insert(next_region_id, Region { id: next_region_id, edges: edge_count, count: 1 }); + next_region_id += 1; + + update_neighbors(&mut plots, &plot, &mut regions); + } + + let w = plots.iter().map(|p| p.iter().map(|p| p.letter).collect::()).join("\n"); + println!("{}", w); + + let total = regions.iter().sorted_by_key(|r| r.0).fold(0, |sum, (region_id, region)| { + let start_plot = find_plot(&plots, |p| p.region_id == Some(*region_id)).unwrap(); + print!("REGION {}:", region.id); + let edges = traverse_start(&plots, &start_plot); + let total = sum + (region.count * edges); + println!(" = {} * {} = {}", region.count, edges, region.count * edges); + total + }); + + Some(total as u32) } -fn get_next_unassigned_plot(plots: &Vec>) -> Option<(usize, usize)> { +fn find_plot(plots: &Vec>, predicate: F) -> Option<(usize, usize)> where +F: Fn(&Plot) -> bool { for (row_idx, row) in plots.iter().enumerate() { for (col_idx, plot) in row.iter().enumerate() { - if plot.region_id == None { + if predicate(plot) { // println!("FOUND unassigned plot! ({}, {})", row_idx, col_idx); return Some((row_idx, col_idx)) } @@ -68,7 +108,7 @@ fn update_neighbors(plots: &mut Vec>, plot: &(usize, usize), regions: // println!("UPDATING plot ({}, {}) - {}:{}", plot.0, plot.1, current_region_letter, current_region_id.unwrap()); - let directions: Vec<(isize, isize)> = vec![(-1, 0), (1, 0), (0, -1), (0, 1)]; + let directions: Vec = vec![(-1, 0), (1, 0), (0, -1), (0, 1)]; for d in directions { let new_row_idx = &(plot.0 as isize + d.0); let new_col_idx = &(plot.1 as isize + d.1); @@ -104,10 +144,90 @@ fn count_edges(plots: &Vec>, plot: &(usize, usize)) -> usize { plots[new_row as usize][new_col as usize].letter != plots[plot.0][plot.1].letter }).count() } + +fn traverse_start(plots: &Vec>, plot: &(usize, usize)) -> usize { + let start_direction: Direction = (0, 1); + + let mut prior_plot = plot.clone(); + let mut prior_direction = start_direction.clone(); + + println!(" at {:?} going {}", plot, debug_direction(start_direction)); + + // count the number of times we turn a corner + let mut corners = 0; + while let Some((next_plot, next_direction)) = traverse_step(plots, &prior_plot, prior_direction) { + if next_direction != prior_direction { + corners += 1; + } + if next_plot == *plot && next_direction == start_direction { break } + prior_plot = next_plot; + prior_direction = next_direction; + // let (next_plot, next_direction) = traverse_step(plots, &next_plot, next_direction).unwrap(); + } + corners +} + +fn traverse_step(plots: &Vec>, plot: &(usize, usize), direction: Direction) -> Option<((usize, usize), Direction)> { + let grid_height = plots.len(); + let grid_width = plots[0].len(); + + // RIGHT, UP, LEFT, DOWN, RIGHT + let directions: Vec = vec![(0, 1), (-1, 0), (0, -1), (1, 0), (0, 1)]; + let get_at = |row_idx: isize, col_idx: isize| { + if !(0..grid_height as isize).contains(&row_idx) || !(0..grid_width as isize).contains(&col_idx) { return None; } + let new_plot = &plots[row_idx as usize][col_idx as usize]; + if new_plot.region_id != plots[plot.0][plot.1].region_id { return None; } + Some(new_plot) + }; + + let ccw_curve = directions.windows(2).find(|dd| dd[0] == direction).map(|dd| dd[1]).unwrap(); + let ccw_plot = get_at(plot.0 as isize + ccw_curve.0 as isize, plot.1 as isize + ccw_curve.1 as isize); + let next_plot = get_at(plot.0 as isize + direction.0 as isize, plot.1 as isize + direction.1 as isize); + + // TODO - fix this traversal code + if ccw_plot != None { + // if the CCW plot IS in the region, ROTATE CCW + print!("↺ "); + return Some((*plot, ccw_curve)); + } else if next_plot != None { + // if the NEXT plot IS in the region, MOVE NEXT + print!("{} ", debug_direction(direction)); + return Some((((plot.0 as isize + direction.0 as isize) as usize, (plot.1 as isize + direction.1 as isize) as usize), direction)); + } else { + // if the NEXT plot is NOT in the region, ROTATE CW + let cw_curve = directions.iter().rev().collect_vec().windows(2).find(|dd| *dd[0] == direction).map(|dd| *dd[1]).unwrap(); + print!("↻ "); + return Some((*plot, cw_curve )) + } + + // When we get back to the FIRST plot and are at the same direction, stop counting +} + +fn debug_direction(d: Direction) -> char { + let arrows = HashMap::from([ + ((-1 as isize, 0 as isize), '↑'), + ((1 as isize, 0 as isize), '↓'), + ((0 as isize, -1 as isize), '←'), + ((0 as isize, 1 as isize), '→') + ]); + + *arrows.get(&d).unwrap_or(&'X') +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn test_window() { + let values = vec![1,2,3,4,5,6]; + let result = values.windows(2).find(|a| a[0] == 4).map(|a| a[1]); + assert_eq!(result, Some(5)); + + let result = values.iter().rev().collect_vec().windows(2).find(|a| *a[0] == 4).map(|a| *a[1]); + assert_eq!(result, Some(3)); + } + #[test] fn test_part_one() { let result = part_one(&advent_of_code::template::read_file("examples", DAY)); @@ -129,6 +249,24 @@ mod tests { #[test] fn test_part_two() { let result = part_two(&advent_of_code::template::read_file("examples", DAY)); - assert_eq!(result, None); + assert_eq!(result, Some(80)); + } + + #[test] + fn test_part_two_2() { + let result = part_two(&advent_of_code::template::read_file_part("examples", DAY, 2)); + assert_eq!(result, Some(436)); + } + + #[test] + fn test_part_two_4() { + let result = part_two(&advent_of_code::template::read_file_part("examples", DAY, 4)); + assert_eq!(result, Some(368)); + } + + #[test] + fn test_part_two_5() { + let result = part_two(&advent_of_code::template::read_file_part("examples", DAY, 5)); + assert_eq!(result, Some(1206)); } }