diff --git a/Cargo.lock b/Cargo.lock index 52ef9a5..8c9b048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" name = "advent" version = "0.1.0" dependencies = [ + "clru", "getrandom", "itertools", "num-bigint", @@ -232,6 +233,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "clru" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" + [[package]] name = "colorchoice" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index adf09d3..0cbf181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] members = ["cli", "lib", "wasm"] -resolver = "2" \ No newline at end of file +resolver = "2" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index fc13201..68fffb4 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,6 +11,7 @@ regex = "1.10.2" scraper = "0.18.1" reqwest = "0.11.22" itertools = "0.12.0" +clru = "0.6.1" [dependencies.getrandom] version = "0.2.11" diff --git a/lib/src/day11/mod.rs b/lib/src/day11/mod.rs index 8c30e86..55ba430 100644 --- a/lib/src/day11/mod.rs +++ b/lib/src/day11/mod.rs @@ -38,9 +38,13 @@ impl Day for Day11 { .iter() .enumerate() .flat_map(|(i, line)| { - line.iter() - .enumerate() - .filter_map(move |(j, &c)| if c == '#' { Some((i as isize, j as isize)) } else { None }) + line.iter().enumerate().filter_map(move |(j, &c)| { + if c == '#' { + Some((i as isize, j as isize)) + } else { + None + } + }) }) .collect(); @@ -50,10 +54,10 @@ impl Day for Day11 { .map(|c| (c[0], c[1])) .collect::>(); - let result = combinations.into_iter().map( - |(start, end)| - (end.0 - start.0).abs() + (end.1 - start.1).abs() - ).sum::(); + let result = combinations + .into_iter() + .map(|(start, end)| (end.0 - start.0).abs() + (end.1 - start.1).abs()) + .sum::(); result.to_string() } @@ -82,20 +86,28 @@ impl Day for Day11 { .iter() .enumerate() .flat_map(|(i, line)| { - line.iter() - .enumerate() - .filter_map({ - let rows_expanded = &self.rows_expanded; - let columns_expanded = &self.columns_expanded; - move |(j, &c)| { - if c == '#' { - Some(( - i as i128 + (rows_expanded.iter().filter(|&&row| row < i).count() as i128 * 999999i128), - j as i128 + (columns_expanded.iter().filter(|&&column| column < j).count() as i128 * 999999i128) - )) - } else { None } + line.iter().enumerate().filter_map({ + let rows_expanded = &self.rows_expanded; + let columns_expanded = &self.columns_expanded; + move |(j, &c)| { + if c == '#' { + Some(( + i as i128 + + (rows_expanded.iter().filter(|&&row| row < i).count() + as i128 + * 999999i128), + j as i128 + + (columns_expanded + .iter() + .filter(|&&column| column < j) + .count() as i128 + * 999999i128), + )) + } else { + None } - }) + } + }) }) .collect(); @@ -105,10 +117,10 @@ impl Day for Day11 { .map(|c| (c[0], c[1])) .collect::>(); - let result = combinations.into_iter().map( - |(start, end)| - (end.0 - start.0).abs() + (end.1 - start.1).abs() - ).sum::(); + let result = combinations + .into_iter() + .map(|(start, end)| (end.0 - start.0).abs() + (end.1 - start.1).abs()) + .sum::(); result.to_string() } diff --git a/lib/src/day12/mod.rs b/lib/src/day12/mod.rs new file mode 100644 index 0000000..baea5ca --- /dev/null +++ b/lib/src/day12/mod.rs @@ -0,0 +1,170 @@ +use clru::CLruCache; +use itertools::{repeat_n, Itertools}; + +use crate::utils::Day; + +#[derive(Debug)] +pub struct Day12 { + pub input: String, + pub cache: CLruCache<(Vec, usize, Vec), usize>, +} + +impl Day12 { + fn calculate( + &mut self, + chars_remaining: &[char], + accumulated: usize, + groups: &[usize], + ) -> usize { + if let Some(&cached) = + self.cache + .get(&(chars_remaining.to_vec(), accumulated, groups.to_vec())) + { + return cached; + } + + let result = if chars_remaining.len() == 0 { + // We have reached the end, determine success + if groups.len() == 0 { + // All groups are gone, success! + 1 + } else { + // Otherwise, we didn't get all the groups, failure :( + 0 + } + } else { + // Continue accumulating and checking groups, adding up both directions when we have a ? + (if chars_remaining[0] != '.' { + // The char on the stack is a #, accumulate + self.calculate(&chars_remaining[1..], accumulated + 1, groups) + } else { + 0 + }) + (if chars_remaining[0] != '#' { + // The char on the stack is a ., either close a group or NOOP + if accumulated != 0 { + // There is a current accumulation, attempt to close the group + if groups.get(0).is_some_and(|&g| g == accumulated) { + // There is a group to close and it is equal to the amount of accumulation, close it and reset accumulation! + self.calculate(&chars_remaining[1..], 0, &groups[1..]) + } else { + // There is no group to close or the accumulation is too low/high, fail this batch + 0 + } + } else { + // There is no current accumulation, NOOP this char + self.calculate(&chars_remaining[1..], 0, groups) + } + } else { + 0 + }) + }; + + self.cache.put( + (chars_remaining.to_vec(), accumulated, groups.to_vec()), + result, + ); + + result + } +} + +impl Day for Day12 { + fn part1(&mut self) -> String { + let parsed = self + .input + .lines() + .map(|line| line.split_once(' ').unwrap()) + .map(|(springs, groups)| { + ( + springs.chars().collect::>(), + groups + .split(',') + .map(|s| s.parse::().unwrap()) + .collect::>(), + ) + }) + .collect::>(); + + let mut result = 0u128; + + for (springs, groups) in parsed { + let unknowns = springs + .iter() + .enumerate() + .filter_map(|(i, &c)| if c == '?' { Some((c, i)) } else { None }) + .count(); + let possible = repeat_n(vec!['#', '.'].into_iter(), unknowns).multi_cartesian_product(); + for permutation in possible { + let mut permutation = permutation.into_iter(); + // For each possible combination, + let springs = springs.iter().map(|char| { + if char == &'?' { + permutation.next().unwrap() + } else { + *char + } + }); + + let grouping = springs.group_by(|&char| char); + let grouped = grouping + .into_iter() + .filter_map(|(c, group)| if c == '#' { Some(group.count()) } else { None }) + .collect::>(); + + if grouped == groups { + result += 1; + } + } + } + + result.to_string() + } + + fn part2(&mut self) -> String { + let parsed = self + .input + .lines() + .map(|line| line.split_once(' ').unwrap()) + .map(|(springs, groups)| { + ( + repeat_n(springs, 5).join("?").chars().collect::>(), + repeat_n(groups, 5) + .join(",") + .split(",") + .map(|s| s.parse::().unwrap()) + .collect::>(), + ) + }) + .map(|(mut springs, groups)| { + ( + { + springs.push('.'); + springs + }, + groups, + ) + }) + .collect::>(); + + // Hey at least I can say I tried the mathy way, but idk enough math for this lol + // let mut result = 0u128; + + // for (springs, groups) in parsed { + // let n = (springs.len() - groups.iter().sum::() + 1) as u128; + // let r = groups.len() as u128; + + // let nonogram_solution_possibilities = (1..=n).product::() / ((1..=(n-r)).product::() * (1..=r).product::()); + // let permutations = (0..(springs.iter().filter(|&&c| c == '?').count())).map(|_| 2 as u128).product::(); + + // dbg!(nonogram_solution_possibilities, 2u128.pow(springs.len() as u32), permutations); + // } + + let mut acc = 0; + + for (springs, groups) in parsed.into_iter() { + acc += self.calculate(springs.as_slice(), 0, groups.as_slice()); + } + + acc.to_string() + } +} diff --git a/lib/src/day13/mod.rs b/lib/src/day13/mod.rs new file mode 100644 index 0000000..5873f52 --- /dev/null +++ b/lib/src/day13/mod.rs @@ -0,0 +1,293 @@ +use itertools::Itertools; + +use crate::utils::Day; + +#[derive(Debug, Default)] +pub struct Day13 { + pub input: String, +} + +impl Day for Day13 { + fn part1(&mut self) -> String { + let pictures = self.input.split("\n\n"); + + pictures + .map(|picture| { + let picture = picture.trim(); + let mut cache = String::new(); + let rows = picture + .lines() + .map(|s| s.to_string()) + .enumerate() + .collect_vec(); + let columns = (0..picture.split_once('\n').unwrap().0.len()) + .map(|i| { + ( + i, + picture + .lines() + .map(|line| line.chars().nth(i).unwrap()) + .join(""), + ) + }) + .collect_vec(); + + // Find all row (horizontal) reflections + let mut reflects_rows = vec![(0usize, 0usize); 0]; + for (i, line) in rows.iter() { + if line == &cache { + reflects_rows.push((i - 1, *i)); + } else { + cache = line.to_string(); + } + } + cache = String::new(); + + // Find all column (vertical) reflections + let mut reflects_columns = vec![(0usize, 0usize); 0]; + for (i, column) in columns.iter() { + if column == &cache { + reflects_columns.push((i - 1, *i)); + } else { + cache = column.clone(); + } + } + + // Expand all row reflections + let rows = reflects_rows + .iter_mut() + .filter_map(|reflection| { + loop { + if reflection.0 != 0 + && reflection.1 != rows.len() - 1 + && rows[reflection.0 - 1].1 == rows[reflection.1 + 1].1 + { + *reflection = (reflection.0 - 1, reflection.1 + 1); + } else { + break; + } + } + + if reflection.0 == 0 || reflection.1 == rows.len() - 1 { + Some(((reflection.0 + reflection.1) / 2) + 1) + } else { + None + } + }) + .sum::(); + + // Expand all column reflections + let columns = reflects_columns + .iter_mut() + .filter_map(|reflection| { + loop { + if reflection.0 != 0 + && reflection.1 != columns.len() - 1 + && columns[reflection.0 - 1].1 == columns[reflection.1 + 1].1 + { + *reflection = (reflection.0 - 1, reflection.1 + 1); + } else { + break; + } + } + + if reflection.0 == 0 || reflection.1 == columns.len() - 1 { + Some(((reflection.0 + reflection.1) / 2) + 1) + } else { + None + } + }) + .sum::(); + + columns + (rows * 100) + }) + .sum::() + .to_string() + } + + fn part2(&mut self) -> String { + let pictures = self.input.split("\n\n"); + + // NOTE: do not short circuit, make sure to actually check if the one-off reflection is valid first + + pictures + .map(|picture| { + let picture = picture.trim(); + let rows = picture + .lines() + .map(|s| s.to_string()) + .enumerate() + .collect_vec(); + let columns = (0..picture.split_once('\n').unwrap().0.len()) + .map(|i| { + ( + i, + picture + .lines() + .map(|line| line.chars().nth(i).unwrap()) + .join(""), + ) + }) + .collect_vec(); + + // Find all row (horizontal) reflections + let mut cache = String::from(" ".repeat(columns.len())); + let mut reflects_rows = vec![(0usize, 0usize, false); 0]; + for (i, line) in rows.iter() { + let mut iter = cache.chars(); + let intersection = line + .chars() + .map(|c| if c == iter.next().unwrap() { c } else { '!' }) + .collect_vec(); + + match intersection.iter().filter(|&&c| c == '!').count() { + 0 => reflects_rows.push((i - 1, *i, false)), + 1 => reflects_rows.push((i - 1, *i, true)), + _ => (), + } + + cache = line.to_string(); + } + + let mut cache = String::from(" ".repeat(rows.len())); + + // Find all column (vertical) reflections + let mut reflects_columns = vec![(0usize, 0usize, false); 0]; + for (i, column) in columns.iter() { + let mut iter = cache.chars(); + let intersection = column + .chars() + .map(|c| if c == iter.next().unwrap() { c } else { '!' }) + .collect_vec(); + + match intersection.iter().filter(|&&c| c == '!').count() { + 0 => reflects_columns.push((i - 1, *i, false)), + 1 => reflects_columns.push((i - 1, *i, true)), + _ => (), + } + + cache = column.to_string(); + } + + // Expand all row reflections + reflects_rows = reflects_rows + .into_iter() + .filter_map(|mut reflection| { + loop { + if reflection.0 == 0 || reflection.1 == rows.len() - 1 { + break; + } + + let mut lower_iter = rows[reflection.0 - 1].1.chars(); + let intersection = rows[reflection.1 + 1] + .1 + .chars() + .map(|c| { + if c == lower_iter.next().unwrap() { + c + } else { + '!' + } + }) + .collect_vec(); + + match ( + intersection.iter().filter(|&&c| c == '!').count(), + reflection.2, + ) { + (1, false) => { + reflection = (reflection.0 - 1, reflection.1 + 1, true) + } + (0, _) => { + reflection = (reflection.0 - 1, reflection.1 + 1, reflection.2) + } + _ => break, + } + } + + if reflection.0 == 0 || reflection.1 == rows.len() - 1 { + Some(( + (reflection.0 + reflection.1) / 2, + ((reflection.0 + reflection.1) / 2) + 1, + reflection.2, + )) + } else { + None + } + }) + .collect(); + + // Expand all column reflections + reflects_columns = reflects_columns + .into_iter() + .filter_map(|mut reflection| { + loop { + if reflection.0 == 0 || reflection.1 == columns.len() - 1 { + break; + } + + let mut lower_iter = columns[reflection.0 - 1].1.chars(); + let intersection = columns[reflection.1 + 1] + .1 + .chars() + .map(|c| { + if c == lower_iter.next().unwrap() { + c + } else { + '!' + } + }) + .collect_vec(); + + match ( + intersection.iter().filter(|&&c| c == '!').count(), + reflection.2, + ) { + (1, false) => { + reflection = (reflection.0 - 1, reflection.1 + 1, true) + } + (0, _) => { + reflection = (reflection.0 - 1, reflection.1 + 1, reflection.2) + } + _ => break, + } + } + + if reflection.0 == 0 || reflection.1 == columns.len() - 1 { + Some(( + (reflection.0 + reflection.1) / 2, + ((reflection.0 + reflection.1) / 2) + 1, + reflection.2, + )) + } else { + None + } + }) + .collect(); + + reflects_rows + .into_iter() + .find_map(|reflection| { + if reflection.2 == true { + Some(reflection.1 * 100) + } else { + None + } + }) + .unwrap_or_else(|| { + reflects_columns + .into_iter() + .find_map(|reflection| { + if reflection.2 == true { + Some(reflection.1) + } else { + None + } + }) + .expect("unable to find reflection wtf") + }) + }) + .sum::() + .to_string() + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a7d6fe7..acd902d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,8 +1,11 @@ -use std::collections::HashMap; +use std::{collections::HashMap, num::NonZeroUsize}; +use clru::CLruCache; use day1::Day1; use day10::Day10; use day11::Day11; +use day12::Day12; +use day13::Day13; use day2::Day2; use day3::Day3; use day4::Day4; @@ -19,6 +22,8 @@ pub mod utils; pub mod day1; pub mod day10; pub mod day11; +pub mod day12; +pub mod day13; pub mod day2; pub mod day3; pub mod day4; @@ -53,7 +58,16 @@ pub fn get_day(day: u8, input: String) -> Box { input, parsed: vec![], }), - 11 => Box::new(Day11 { input, rows_expanded: vec![], columns_expanded: vec![] }), + 11 => Box::new(Day11 { + input, + rows_expanded: vec![], + columns_expanded: vec![], + }), + 12 => Box::new(Day12 { + input, + cache: CLruCache::new(NonZeroUsize::new(10000).unwrap()), + }), + 13 => Box::new(Day13 { input }), _ => panic!("Invalid day #"), } }