Add part 2 solution to day 6
parent
cb6be97cf3
commit
21235c33ea
|
@ -8,6 +8,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"itertools",
|
||||
"nom",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -48,6 +49,61 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
|
|
|
@ -8,3 +8,4 @@ edition = "2021"
|
|||
[dependencies]
|
||||
nom = "7.1"
|
||||
itertools = "0.10"
|
||||
thiserror = "1.0"
|
||||
|
|
332
day8/src/main.rs
332
day8/src/main.rs
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
@ -13,15 +14,140 @@ use nom::{
|
|||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
const SEGMENT_CHARS: &[char] = &['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SignalInfo {
|
||||
signal_patterns: Vec<String>,
|
||||
output_values: Vec<String>,
|
||||
}
|
||||
|
||||
struct SevenSegmentSignals {
|
||||
top: char,
|
||||
top_right: char,
|
||||
bottom_right: char,
|
||||
bottom: char,
|
||||
bottom_left: char,
|
||||
top_left: char,
|
||||
middle: char,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum DecodeError {
|
||||
#[error("Invalid configuraton string given: {0}")]
|
||||
InavlidConfiguration(String),
|
||||
}
|
||||
|
||||
impl SevenSegmentSignals {
|
||||
fn decode_str(&self, s: &str) -> Result<u8, DecodeError> {
|
||||
// Could these be members? yes. Are they? no.
|
||||
let zero = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_left,
|
||||
self.bottom_left,
|
||||
self.bottom,
|
||||
self.bottom_right,
|
||||
self.top_right,
|
||||
]);
|
||||
|
||||
let one = Self::make_segment_str(&[self.top_right, self.bottom_right]);
|
||||
|
||||
let two = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_right,
|
||||
self.middle,
|
||||
self.bottom_left,
|
||||
self.bottom,
|
||||
]);
|
||||
|
||||
let three = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_right,
|
||||
self.middle,
|
||||
self.bottom_right,
|
||||
self.bottom,
|
||||
]);
|
||||
|
||||
let four = Self::make_segment_str(&[
|
||||
self.top_left,
|
||||
self.middle,
|
||||
self.top_right,
|
||||
self.bottom_right,
|
||||
]);
|
||||
|
||||
let five = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_left,
|
||||
self.middle,
|
||||
self.bottom_right,
|
||||
self.bottom,
|
||||
]);
|
||||
|
||||
let six = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_left,
|
||||
self.middle,
|
||||
self.bottom_left,
|
||||
self.bottom,
|
||||
self.bottom_right,
|
||||
]);
|
||||
|
||||
let seven = Self::make_segment_str(&[self.top, self.top_right, self.bottom_right]);
|
||||
|
||||
let eight = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_left,
|
||||
self.bottom_left,
|
||||
self.middle,
|
||||
self.bottom,
|
||||
self.bottom_right,
|
||||
self.top_right,
|
||||
]);
|
||||
|
||||
let nine = Self::make_segment_str(&[
|
||||
self.top,
|
||||
self.top_left,
|
||||
self.middle,
|
||||
self.bottom,
|
||||
self.bottom_right,
|
||||
self.top_right,
|
||||
]);
|
||||
|
||||
let sorted_str = s.chars().sorted().collect::<String>();
|
||||
if sorted_str == one {
|
||||
Ok(1)
|
||||
} else if sorted_str == two {
|
||||
Ok(2)
|
||||
} else if sorted_str == three {
|
||||
Ok(3)
|
||||
} else if sorted_str == four {
|
||||
Ok(4)
|
||||
} else if sorted_str == five {
|
||||
Ok(5)
|
||||
} else if sorted_str == six {
|
||||
Ok(6)
|
||||
} else if sorted_str == seven {
|
||||
Ok(7)
|
||||
} else if sorted_str == eight {
|
||||
Ok(8)
|
||||
} else if sorted_str == nine {
|
||||
Ok(9)
|
||||
} else if sorted_str == zero {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(DecodeError::InavlidConfiguration(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_segment_str(segments: &[char]) -> String {
|
||||
segments.iter().sorted().collect::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_segment_char(c: char) -> bool {
|
||||
('a'..='g').contains(&c)
|
||||
SEGMENT_CHARS.contains(&c)
|
||||
}
|
||||
|
||||
fn parse_signal_block(chunk: &str) -> IResult<&str, &str> {
|
||||
|
@ -39,20 +165,15 @@ fn parse_line(line: &str) -> IResult<&str, SignalInfo> {
|
|||
)(line)?;
|
||||
|
||||
let parsed_info = SignalInfo {
|
||||
signal_patterns: raw_signal_patterns
|
||||
.into_iter()
|
||||
.map(|s| s.to_owned())
|
||||
.collect(),
|
||||
output_values: raw_output_values
|
||||
.into_iter()
|
||||
.map(|s| s.to_owned())
|
||||
.collect(),
|
||||
signal_patterns: raw_signal_patterns.into_iter().map(str::to_owned).collect(),
|
||||
output_values: raw_output_values.into_iter().map(str::to_owned).collect(),
|
||||
};
|
||||
|
||||
Ok(("", parsed_info))
|
||||
}
|
||||
|
||||
fn determine_signal_mapping(signal_patterns: &[String]) -> HashMap<String, u8> {
|
||||
/// Find the signal mappings that can be easily known by their number of segments
|
||||
fn determine_simple_signal_mappings(signal_patterns: &[String]) -> HashMap<String, u8> {
|
||||
signal_patterns
|
||||
.iter()
|
||||
.filter_map(|signal_pattern| {
|
||||
|
@ -68,11 +189,18 @@ fn determine_signal_mapping(signal_patterns: &[String]) -> HashMap<String, u8> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn transpose_signal_map(original: &HashMap<String, u8>) -> HashMap<u8, String> {
|
||||
original
|
||||
.iter()
|
||||
.map(|(s, &count)| (count, s.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn part1(signal_infos: &[SignalInfo]) -> usize {
|
||||
signal_infos
|
||||
.iter()
|
||||
.map(|signal_info| {
|
||||
let signal_mapping = determine_signal_mapping(&signal_info.signal_patterns);
|
||||
let signal_mapping = determine_simple_signal_mappings(&signal_info.signal_patterns);
|
||||
signal_info
|
||||
.output_values
|
||||
.iter()
|
||||
|
@ -85,6 +213,182 @@ fn part1(signal_infos: &[SignalInfo]) -> usize {
|
|||
.sum()
|
||||
}
|
||||
|
||||
fn make_char_set(s: &str) -> HashSet<char> {
|
||||
s.chars().collect()
|
||||
}
|
||||
|
||||
// Infer all of the segements from a signal info
|
||||
// This is long and makes many assertions. I don't have the patience to clean it up at this moment.
|
||||
// If I was feeling less lazy, I'd turn these assertions into Results on the error type. Maybe later.
|
||||
//
|
||||
// This is just advent of code after all :)
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn infer_segments(signal_info: &SignalInfo) -> SevenSegmentSignals {
|
||||
let signal_mapping = determine_simple_signal_mappings(&signal_info.signal_patterns);
|
||||
let num_to_signal_map = transpose_signal_map(&signal_mapping);
|
||||
|
||||
let one_signals = make_char_set(
|
||||
num_to_signal_map
|
||||
.get(&1)
|
||||
.expect("no mapping for 1 was determined"),
|
||||
);
|
||||
let seven_signals = make_char_set(
|
||||
num_to_signal_map
|
||||
.get(&7)
|
||||
.expect("no mapping for 7 was determined"),
|
||||
);
|
||||
let seven_one_difference = seven_signals
|
||||
.difference(&one_signals)
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(seven_one_difference.len(), 1);
|
||||
|
||||
let top_segment = **seven_one_difference.iter().next().unwrap();
|
||||
println!("top => {}", top_segment);
|
||||
|
||||
let four_signals = make_char_set(
|
||||
num_to_signal_map
|
||||
.get(&4)
|
||||
.expect("no mapping for 4 was determined"),
|
||||
);
|
||||
|
||||
// This will have the two segments that don't have the right "stick" of the four.
|
||||
let four_one_difference = four_signals
|
||||
.difference(&one_signals)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(four_one_difference.len(), 2);
|
||||
|
||||
// There are three items that use six segments: 0 and 6, 9. Only zero matches only the lefthand "prong" of the four,
|
||||
// so the one with one intersection will disambiguate that one
|
||||
let six_element_char_sets = signal_info
|
||||
.signal_patterns
|
||||
.iter()
|
||||
.filter(|s| s.len() == 6)
|
||||
.map(|s| make_char_set(s))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(six_element_char_sets.len(), 3);
|
||||
|
||||
let one_element_left_from_six_set = six_element_char_sets
|
||||
.iter()
|
||||
.map(|set| {
|
||||
four_one_difference
|
||||
.difference(set)
|
||||
.copied()
|
||||
.collect::<HashSet<char>>()
|
||||
})
|
||||
.filter(|set| set.len() == 1)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(one_element_left_from_six_set.len(), 1);
|
||||
let middle_segment_set = &one_element_left_from_six_set[0];
|
||||
assert_eq!(middle_segment_set.len(), 1);
|
||||
let middle_segment = *middle_segment_set.iter().next().unwrap();
|
||||
println!("middle => {}", middle_segment);
|
||||
|
||||
// Now that we know the middle, the only element left in the original "four one difference" set will be the top left
|
||||
let top_left_segment_set = four_one_difference
|
||||
.iter()
|
||||
.filter(|&&c| c != middle_segment)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(top_left_segment_set.len(), 1);
|
||||
let top_left_segment = *top_left_segment_set[0];
|
||||
println!("top left => {}", top_left_segment);
|
||||
|
||||
// Finding the top right segment is pretty easy. If we consider the segments of the "one", there is only one
|
||||
// six-element segment which these sets is not a super-set of the "one": the top right.
|
||||
let five_signals_set = six_element_char_sets
|
||||
.iter()
|
||||
.filter(|set| !set.is_superset(&one_signals))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(five_signals_set.len(), 1);
|
||||
|
||||
let five_signals = five_signals_set[0];
|
||||
let top_right_difference = one_signals.difference(five_signals).collect::<HashSet<_>>();
|
||||
assert_eq!(top_right_difference.len(), 1);
|
||||
let top_right_segment = **top_right_difference.iter().next().unwrap();
|
||||
println!("top right => {}", top_right_segment);
|
||||
|
||||
// And of course, knowing the top right, we know the bottom right, given there's only one other element in the one.
|
||||
let bottom_right_set = one_signals
|
||||
.iter()
|
||||
.filter(|&&c| c != top_right_segment)
|
||||
.collect::<HashSet<_>>();
|
||||
let bottom_right_segment = **bottom_right_set.iter().next().unwrap();
|
||||
println!("bottom right => {}", bottom_right_segment);
|
||||
|
||||
// Now that we know the bottom and top left, of the six signal elements, we can uniquely identify the nine.
|
||||
// Of our possible input signals, the only one it _doesn't_ have must be the bottom left.
|
||||
let nine_char_set_set = six_element_char_sets
|
||||
.iter()
|
||||
.filter(|set| set.contains(&top_right_segment) && set.contains(&middle_segment))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(nine_char_set_set.len(), 1);
|
||||
|
||||
let nine_char_set = nine_char_set_set[0];
|
||||
let segment_chars_set = SEGMENT_CHARS.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
let bottom_left_set = segment_chars_set
|
||||
.difference(nine_char_set)
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(bottom_left_set.len(), 1);
|
||||
let bottom_left_segment = **bottom_left_set.iter().next().unwrap();
|
||||
println!("bottom_left => {}", bottom_left_segment);
|
||||
|
||||
// aaaand all that's left is the bottom
|
||||
let all_but_bottom = vec![
|
||||
top_segment,
|
||||
top_right_segment,
|
||||
bottom_right_segment,
|
||||
bottom_left_segment,
|
||||
top_left_segment,
|
||||
middle_segment,
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let bottom_set = segment_chars_set
|
||||
.difference(&all_but_bottom)
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(bottom_set.len(), 1);
|
||||
|
||||
let bottom_segment = **bottom_set.iter().next().unwrap();
|
||||
|
||||
println!("bottom => {}", bottom_segment);
|
||||
|
||||
SevenSegmentSignals {
|
||||
top: top_segment,
|
||||
top_right: top_right_segment,
|
||||
bottom_right: bottom_right_segment,
|
||||
bottom: bottom_segment,
|
||||
bottom_left: bottom_left_segment,
|
||||
top_left: top_left_segment,
|
||||
middle: middle_segment,
|
||||
}
|
||||
}
|
||||
|
||||
fn part2(signal_infos: &[SignalInfo]) -> u32 {
|
||||
signal_infos
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, signal_info)| {
|
||||
println!("Item {}", i + 1);
|
||||
let segments = infer_segments(signal_info);
|
||||
let res = signal_info
|
||||
.output_values
|
||||
.iter()
|
||||
.map(|output| {
|
||||
segments
|
||||
.decode_str(output)
|
||||
.unwrap_or_else(|err| panic!("Failed to decode {}: {:?}", output, err))
|
||||
.into()
|
||||
})
|
||||
.fold(0_u32, |total, n: u32| (total * 10) + n);
|
||||
|
||||
println!("{}\n", res);
|
||||
res
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input_file_name = env::args().nth(1).expect("No input filename specified");
|
||||
let input_file = File::open(input_file_name).expect("Could not open input file");
|
||||
|
@ -98,6 +402,6 @@ fn main() {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// println!("{:?}", input_lines);
|
||||
println!("Part 1: {}", part1(&input_lines));
|
||||
println!("Part 2: {}", part2(&input_lines));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue