diff --git a/data/examples/12.txt b/data/examples/12.txt new file mode 100644 index 0000000..85b768f --- /dev/null +++ b/data/examples/12.txt @@ -0,0 +1,10 @@ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE diff --git a/src/bin/12.rs b/src/bin/12.rs new file mode 100644 index 0000000..eb20d99 --- /dev/null +++ b/src/bin/12.rs @@ -0,0 +1,317 @@ +advent_of_code::solution!(12); + +use itertools::Itertools; +use std::collections::HashMap; +use std::iter::successors; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Pos { + x: isize, + y: isize, +} + +impl Pos { + fn origin() -> Self { + Self { x: 0, y: 0 } + } + + fn diff(&self, other: &Self) -> Self { + Self { + x: self.x - other.x, + y: self.y - other.y, + } + } + + fn init(x: usize, y: usize) -> Self { + Self { + x: x as isize, + y: y as isize, + } + } + + fn neighbors(&self) -> Vec { + vec![ + Pos { + x: self.x - 1, + y: self.y, + }, + Pos { + x: self.x, + y: self.y - 1, + }, + Pos { + x: self.x + 1, + y: self.y, + }, + Pos { + x: self.x, + y: self.y + 1, + }, + ] + } + + fn add(&self, other: &Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +#[derive(Debug)] +struct Region { + c: char, + ps: Vec, +} + +impl Region { + fn init(c: char, p: Pos) -> Self { + Self { c, ps: vec![p] } + } + + fn fill(&mut self, remains: &mut Vec<(char, Pos)>) { + if remains.is_empty() { + return; + } + let mut found: bool = false; + for i in 0..remains.len() { + if remains[i].0 == self.c + && remains[i].1.neighbors().iter().any(|p| self.ps.contains(p)) + { + let (_, p) = remains.remove(i); + self.ps.push(p); + found = true; + break; + } + } + if found { + self.fill(remains); + } + } + + fn area(&self) -> usize { + let area = self.ps.len(); + // println!("Region {:?} -> area: {:?}", self.c, area); + area + } + + fn perimeter(&self) -> usize { + self.ps + .clone() + .into_iter() + .flat_map(|p| p.neighbors()) + .filter(|p| !self.ps.contains(p)) + .collect::>() + .len() + } + + fn sides(&self) -> usize { + // Count sides or count angles... + + // Outward: + + // .X + // XAA <- 2 neighbors are missing from region -> 1 angle + // .AA + // XAX <- 3 neighbors are missing from region -> 2 angles + // .X. + + // .A. + // XAX <- 2 neighbors are missing BUT coords cancel out -> 0 angles + // .A. + + // .X. + // XAX <- 4 neighbors missing -> 4 angles ... + // .X. + + // Inward: + + // .XA <- same foreign neighbor for 2 pieces -> 1 angle + // AAA + + // AXA <- same foreign neighbor for 3 pieces -> 2 angles + // AAA + + // AAA + // AXA <- same foreign neighbor for 4 pieces -> 4 angles + // AAA + + // println!("Region {}", self.c); + + let data: Vec<(Pos, Vec)> = self + .ps + .clone() + .into_iter() + .map(|p| { + ( + p.clone(), + p.neighbors() + .into_iter() + .filter(|pn| !self.ps.contains(pn)) + .collect::>(), + ) + }) + .collect(); + + let mut neighbors_map: HashMap> = HashMap::new(); + let mut outward_angles = 0; + data.iter().for_each(|(p, ps)| { + let l = match ps.len() { + 2 => { + match ps + .into_iter() + .fold(Pos::origin(), |acc, p2| acc.add(&p.diff(p2))) + { + Pos { x: 0, y: 0 } => 0, + _ => 1, + } + } + 3 => 2, + 4 => 4, + _ => 0, + }; + let neighbors_vec = ps + .into_iter() + .fold(Pos::origin(), |acc, p2| acc.add(&p.diff(p2))); + // println!(" * Outward angle(s) around: {:?} -> {}", p, l); + // println!(" Neighbors vec: {:?}", neighbors_vec); + outward_angles += l; + for pn in ps { + neighbors_map.entry(pn.clone()).or_default().push(p.clone()); + } + }); + // println!(" Outward Angles: {}", outward_angles); + let inward_angles = neighbors_map + .values() + .map(|ps| { + let l = match ps.len() { + 2 => 1, + 3 => 2, + 4 => 4, + _ => 0, + }; + // println!(" * Inward angle(s) around: ?? -> {}", l); + l + }) + .sum::(); + // println!("Region {:} -> inward angles: {}, outward angles: {}", self.c, inward_angles, outward_angles); + // println!(" -> sides: {}", inward_angles + outward_angles); + inward_angles + outward_angles + } +} + +fn count_sides(path: &mut Vec) -> usize { + if path.len() <= 1 { + return 0; + } + + let start = path.remove(0); + let directions = [ + Pos { x: 0, y: 1 }, // up + Pos { x: 1, y: 0 }, // right + Pos { x: 0, y: -1 }, // down + Pos { x: -1, y: 0 }, // left + ]; + let steps = successors(Some((start, 1, None)), |(cur, sides, d_idx)| { + // println!("Current: {:?}", cur); + if path.is_empty() { + return None; + } + for i in 0..4 { + let dir = directions[(d_idx.unwrap_or(0) + i) % 4].clone(); + let next = cur.add(&dir); + if let Some(ofs) = path.iter().position(|p| p == &next) { + // println!(" Trying direction {:?} -> {:?} (Found!)", dir, next); + let new_cur = path.remove(ofs); + let new_sides = sides + + if d_idx.is_some() && i > 0 { + // println!("(new direction, incrementing sides: {:?})", sides + 1); + 1 + } else { + // println!("(same direction, sides: {:?})", sides); + 0 + }; + return Some((new_cur, new_sides, Some(d_idx.unwrap_or(0) + i))); + // } else { + // println!(" Trying direction {:?} -> {:?} (Not found)", dir, next); + } + } + let new_cur = path.remove(0); + // println!("No consecutive neighbor found -> Trying new side (sides: {})", sides + 1); + Some((new_cur, sides + 1, None)) + }) + .collect::>(); + steps.last().unwrap().1 +} + +fn parse_input(input: &str) -> Vec { + let mut res: Vec = Vec::new(); + let mut pcs = input + .strip_suffix("\n") + .unwrap_or(input) + .lines() + .enumerate() + .flat_map(|(y, line)| { + line.chars() + .enumerate() + .map(|(x, c)| (c, Pos::init(x, y))) + .collect::>() + }) + .collect::>(); + // pcs.sort(); + while !pcs.is_empty() { + let (c, p) = pcs.remove(0); + let mut r: Region = Region::init(c, p); + r.fill(&mut pcs); // Should filter only by relevant letters + res.push(r); + } + res +} + +pub fn part_one(input: &str) -> Option { + parse_input(input) + .iter() + .map(|r| r.area() * r.perimeter()) + .sum::() + .into() +} + +pub fn part_two(input: &str) -> Option { + // println!("{}", input); + parse_input(input) + .iter() + .map(|r| r.area() * r.sides()) + .sum::() + .into() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(1930)); + // assert_eq!(result, Some(140)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(1206)); + // assert_eq!(result, Some(80)); + } +} + +// A region of R plants with price 12 * 10 = 120. => KO 12 * 11 +// A region of I plants with price 4 * 4 = 16. => OK +// A region of C plants with price 14 * 22 = 308. => OK +// A region of F plants with price 10 * 12 = 120. => KO 10 * 13 +// A region of V plants with price 13 * 10 = 130. => KO 13 * 11 +// A region of J plants with price 11 * 12 = 132. => OK +// A region of C plants with price 1 * 4 = 4. => OK +// A region of E plants with price 13 * 8 = 104. => KO 13 * 9 +// A region of I plants with price 14 * 16 = 224. => OK +// A region of M plants with price 5 * 6 = 30. => KO 5 * 7 +// A region of S plants with price 3 * 6 = 18. => OK