diff --git a/source/day_08/display.rs b/source/day_08/display.rs new file mode 100644 index 0000000..4ff0c23 --- /dev/null +++ b/source/day_08/display.rs @@ -0,0 +1,111 @@ +use std::collections::{HashMap, HashSet}; + +use color_eyre::Result; + +pub type CharSet = HashSet; + +#[derive(Debug)] +pub struct Display(pub isize, pub CharSet); + +impl Display { + pub fn parse(s: &str) -> Self { + Self(s.len() as isize, CharSet::from_iter(s.chars())) + } + + pub fn figure_out_from_others( + others: Vec, + ) -> Result> { + let mut displays = HashMap::::new(); + + // Loop over all displays infinitely and stop when we've figured out all 10. + for display in others.iter().cycle() { + if displays.len() == 10 { + break; + } + + let length = display.0; + let charset = display.1.clone(); + + // First check for the known unique segment lengths and insert them. + if let Some(key) = match length { + 2 => Some(1), + 4 => Some(4), + 3 => Some(7), + 7 => Some(8), + _ => None, + } { + displays.insert(key, charset); + continue; + } + + // Without the 1 and 4 segments we can't do anything else so grab those + // or continue the loop. + let (one, four) = match (displays.get(&1), displays.get(&4)) { + (Some(one), Some(four)) => (one, four), + _ => continue, + }; + + // Create the subset we'll use to figure out 0 and 5 using the difference + // between 4 and 1. + let five_zero_subset = CharSet::from_iter(four.difference(one).copied()); + + // The segments for 2, 3 and 5 all have a length of 5. + if length == 5 { + // Only 3 has 1 as its subset. + if one.is_subset(&charset) { + displays.insert(3, charset); + continue; + } + + // Then only 5 will have the subset created earlier. + if five_zero_subset.is_subset(&charset) { + displays.insert(5, charset); + continue; + } + + // Then to figure out 2 we need to know both 3 and 5 so continue if we + // haven't gotten them yet. + let (three, five) = match (displays.get(&3), displays.get(&5)) { + (Some(three), Some(five)) => (three, five), + _ => continue, + }; + + // Then 2 will simply be the remaining set of length 5 as long as it + // doesn't equal 3 or 5. + if ![three, five].contains(&&charset) { + displays.insert(2, charset); + continue; + } + } + + // The segments for 0, 6 and 9 all have length 6. + if length == 6 { + // Only 6 *does not* have the subset for 1. + if !one.is_subset(&charset) { + displays.insert(6, charset); + continue; + } + + // Then 0 *does not* have this subset from earlier. + if !five_zero_subset.is_subset(&charset) { + displays.insert(0, charset); + continue; + } + + let (zero, six) = match (displays.get(&0), displays.get(&6)) { + (Some(zero), Some(six)) => (zero, six), + _ => continue, + }; + + // And again to figure out the remaining segment we use the other 2 + // we know now. + if ![zero, six].contains(&&charset) { + displays.insert(9, charset); + continue; + } + } + } + + Ok(displays) + } +} diff --git a/source/day_08/mod.rs b/source/day_08/mod.rs new file mode 100644 index 0000000..6eea49b --- /dev/null +++ b/source/day_08/mod.rs @@ -0,0 +1,72 @@ +use color_eyre::{eyre::eyre, Result}; + +mod display; + +use display::{CharSet, Display}; + +pub fn solve() -> Result<()> { + let input_data = include_str!("../../data/day_08.txt").trim(); + println!("Day 08 Part 1: {}", part_1(input_data)?); + println!("Day 08 Part 2: {}", part_2(input_data)?); + Ok(()) +} + +fn part_1(input: &str) -> Result { + Ok( + input + .lines() + .map(|line| { + line + .split(" | ") + .nth(1) + .ok_or(eyre!("Invalid input: {}", line)) + }) + .collect::>>()? + .into_iter() + .flat_map(|line| line.split(" ").map(Display::parse)) + .filter(|display| [2, 4, 3, 7].contains(&display.0)) + .count(), + ) +} + +fn part_2(input: &str) -> Result { + let mut sum = 0; + + for line in input.lines() { + let mut split = line.split(" | "); + + // Figure out the displays from the signal side. + let displays = Display::figure_out_from_others( + split + .next() + .ok_or(eyre!("Invalid input: {}", line))? + .split(" ") + .map(Display::parse) + .collect::>(), + )?; + + // Get all the CharSets from the encoded side. + let encoded = split + .next() + .ok_or(eyre!("Invalid input: {}", line))? + .split(" ") + .map(str::chars) + .map(CharSet::from_iter) + .collect::>(); + + // Loop through the encoded numbers backwards so we can use the loop index + // to multiply it by 10 to the power of the index. + // So 123 would be (3 * 1) + (2 * 10) + (1 * 100). + for (index, set) in encoded.into_iter().rev().enumerate() { + let decoded_number = displays + .iter() + .find(|display| display.1 == &set) + .map(|display| display.0) + .ok_or(eyre!("Impossible to decode {:?}", set))?; + + sum += 10_isize.pow(index as u32) * decoded_number; + } + } + + Ok(sum) +} diff --git a/source/main.rs b/source/main.rs index baef111..d44f3b3 100644 --- a/source/main.rs +++ b/source/main.rs @@ -9,6 +9,7 @@ mod day_04; mod day_05; mod day_06; mod day_07; +mod day_08; fn main() -> Result<()> { color_eyre::install()?; @@ -23,6 +24,7 @@ fn main() -> Result<()> { day_05::solve, day_06::solve, day_07::solve, + day_08::solve, ]; for day in days {