diff --git a/2023/src/day13.rs b/2023/src/day13.rs new file mode 100644 index 0000000..e43d9a1 --- /dev/null +++ b/2023/src/day13.rs @@ -0,0 +1,336 @@ +use aoc_runner_derive::{aoc, aoc_generator}; + +type Board = Vec>; +type Pair = (usize, usize); + +#[derive(Debug)] +struct Map { + rows: Board, + cols: Board, + width: usize, + height: usize, +} + +impl Map { + fn find_mirror(&self, board: &Board) -> Option { + let mut matches: Vec = vec![]; + let mut start_index = 0; + while let Some(m) = self.find_row(board, start_index, 0) { + start_index = m.1; + matches.push(m); + } + + let mut res: Vec> = vec![]; + for &(r1, r2) in matches.iter() { + let mut rows_matched = 0; + let mut found = true; + loop { + let start = r1 - rows_matched; + let end = r2 + rows_matched; + if board[start] == board[end] { + rows_matched += 1; + } else { + found = false; + break; + } + if start == 0 || end == board.len() - 1 { + break; + } + } + + if found { + res.push(Some((r1, r2))); + } + } + + if res.len() > 1 { + panic!("got too many matches {:?}", res); + } else if res.is_empty() { + None + } else { + res[0] + } + } + + fn find_row(&self, board: &Board, index: usize, max_diffs: usize) -> Option { + let row_len = board[0].len(); + for t in index..(board.len() - 1) { + let mut diff_chars = 0; + for k in 0..row_len { + if board[t][k] == board[t + 1][k] { + continue; + } + diff_chars += 1; + if diff_chars > max_diffs { + break; + } + } + if diff_chars <= max_diffs { + return Some((t, t + 1)); + } + } + None + } + + fn get_horiz_score(&self) -> Option { + if let Some((_r1, r2)) = self.find_mirror(&self.rows) { + return Some(100 * r2); + } + None + } + + fn get_vert_score(&self) -> Option { + if let Some((_c1, c2)) = self.find_mirror(&self.cols) { + return Some(c2); + } + None + } + + fn part1(&self) -> usize { + if self.width < self.height { + if let Some(n) = self.get_horiz_score() { + return n; + } + if let Some(n) = self.get_vert_score() { + return n; + } + } else { + if let Some(n) = self.get_vert_score() { + return n; + } + if let Some(n) = self.get_horiz_score() { + return n; + } + } + 0 + } + + fn find_smudge(&self, board: &Board) -> Option { + let mut matches: Vec = vec![]; + let mut start_index = 0; + while let Some(m) = self.find_row(board, start_index, 1) { + start_index = m.1; + matches.push(m); + } + + println!("matches {:?}", matches); + let mut res: Vec> = vec![]; + for &(r1, r2) in matches.iter() { + let mut rows_matched = 0; + let mut diff_chars = 0; + loop { + let start = r1 - rows_matched; + let end = r2 + rows_matched; + println!( + "comparing {} to {} with {} and {}", + start, + end, + board[start].iter().collect::(), + board[end].iter().collect::(), + ); + if board[start] == board[end] { + rows_matched += 1; + } else { + for index in 0..board[start].len() { + if board[start][index] != board[end][index] { + diff_chars += 1; + } + } + if diff_chars > 1 { + break; + } + rows_matched += 1; + } + if start == 0 || end == board.len() - 1 { + break; + } + } + println!( + "{},{} rows matched {} diff_chars {}", + r1, r2, rows_matched, diff_chars + ); + + if diff_chars == 1 { + res.push(Some((r1, r2))); + } + } + + if res.len() > 1 { + panic!("got too many matches {:?}", res); + } else if res.is_empty() { + None + } else { + res[0] + } + } + + fn get_horiz_smudge(&self) -> Option { + println!("get horiz smudge"); + if let Some((r1, r2)) = self.find_smudge(&self.rows) { + println!("got horiz smudge rows of {},{}", r1, r2); + return Some(100 * r2); + } + None + } + + fn get_vert_smudge(&self) -> Option { + println!("get vert smudge"); + if let Some((c1, c2)) = self.find_smudge(&self.cols) { + println!("got vert smudge cols of {},{}", c1, c2); + return Some(c2); + } + None + } + + fn part2(&self) -> usize { + println!(); + for line in self.rows.iter() { + println!("{}", line.iter().collect::()); + } + println!("width {} height {}", self.width, self.height); + let hscore = self.get_horiz_smudge(); + let vscore = self.get_vert_smudge(); + if hscore.is_some() && vscore.is_some() { + panic!("got both scores!"); + } + if hscore.is_some() { + hscore.unwrap() + } else if vscore.is_some() { + vscore.unwrap() + } else { + 0 + } + } +} + +fn gen_map(cur: &Board) -> Map { + let width = cur[0].len(); + let height = cur.len(); + let mut cols: Board = vec![]; + for row in 0..width { + cols.push(vec![]); + for col in 0..height { + cols[row].push(cur[col][row]); + } + } + Map { + rows: cur.clone(), + cols, + width, + height, + } +} + +// don't forget to add an extra empty newline +#[aoc_generator(day13)] +fn parse(input: &str) -> Vec { + let mut result: Vec = vec![]; + let mut cur: Board = vec![]; + for line in input.lines() { + if line.is_empty() { + if !cur.is_empty() { + result.push(gen_map(&cur)); + cur.clear(); + } + continue; + } + cur.push( + line.chars() + .filter_map(|t| (!t.is_ascii_whitespace()).then_some(t)) + .collect(), + ); + } + if !cur.is_empty() { + result.push(gen_map(&cur)); + } + result +} + +#[aoc(day13, part1)] +fn part1(input: &[Map]) -> usize { + input.iter().map(|t| t.part1()).sum() +} + +#[aoc(day13, part2)] +fn part2(input: &[Map]) -> usize { + input.iter().map(|t| t.part2()).sum() +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_DATA: &'static str = r#"#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..# + +"#; + + #[test] + fn sample_data() { + let input = parse(&SAMPLE_DATA); + assert_eq!(part1(&input), 405); + assert_eq!(part2(&input), 400); + } + + const SAMPLE_DATA2: &'static str = r#"#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..# + +.#.##.#.# +.##..##.. +.#.##.#.. +#......## +#......## +.#.##.#.. +.##..##.# + +#..#....# +###..##.. +.##.##### +.##.##### +###..##.. +#..#....# +#..##...# + +#.##..##. +..#.##.#. +##..#...# +##...#..# +..#.##.#. +..##..##. +#.#.##.#. + +"#; + + #[test] + fn sample_data2() { + let input = parse(&SAMPLE_DATA2); + assert_eq!(part1(&input), 709); + // assert_eq!(part2(&input), 1400); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index 047c882..8942eda 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -12,5 +12,6 @@ mod day09; mod day10; mod day11; mod day12; +mod day13; aoc_lib! { year = 2023 }