+use std::error::Error;
+use std::path::Path;
+use std::collections::HashMap;
+
+const WIDTH: u8 = 5;
+const HEIGHT: u8 = 7;
+
+struct Puzzle {
+ locks: Vec<Vec<u8>>,
+ keys: Vec<Vec<u8>>,
+}
+
+impl Puzzle {
+ pub fn new(input: &str) -> Self {
+ let mut locks: Vec<Vec<u8>> = Vec::new();
+ let mut keys: Vec<Vec<u8>> = Vec::new();
+
+ input.split("\n\n").for_each(|b| {
+ let mut block: HashMap<(u8, u8), u8> = HashMap::new();
+ b.lines().enumerate()
+ .for_each(|(row, line)| {
+ line.as_bytes().iter().enumerate()
+ .for_each(|(col, value)| {
+ block.insert((row as u8, col as u8), *value);
+ });
+ });
+
+ let mut heights: Vec<u8> = Vec::new();
+ (0..WIDTH).for_each(|i| {
+ heights.push(
+ block.iter().filter(|&((_, col), value)| {
+ *col == i && *value == b'#'
+ })
+ .count() as u8 - 1 // remove the base row
+ );
+ });
+
+ match *block.get(&(0,0)).unwrap() {
+ b'#' => locks.push(heights),
+ b'.' => keys.push(heights),
+ _ => unreachable!(),
+ }
+
+ });
+
+ Self { locks, keys }
+ }
+
+ fn unique_key_lock_pairs(&self) -> u32 {
+ let mut count: u32 = 0;
+ self.locks.iter().for_each(|lock| {
+ count += self.keys.iter().filter(|key| {
+ key.iter().zip(lock.iter()).all(|(k, l)| k + l + 1 < HEIGHT)
+ })
+ .count() as u32;
+ });
+ count
+ }
+}
+
+fn run_part1(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 1", get_day());
+
+ let puzzle = Puzzle::new(input);
+ Ok(puzzle.unique_key_lock_pairs())
+}
+
+fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 2", get_day());
+
+ Ok(0)
+}
+
+pub fn run(input: &str) -> Result<(), Box<dyn Error>> {
+ let res = run_part1(input)?;
+ println!("{res}");
+
+ let res = run_part2(input)?;
+ println!("{res}");
+
+ Ok(())
+}
+
+fn get_day() -> String {
+ let filename = file!();
+ Path::new(filename).file_stem().unwrap().to_str().unwrap().to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEXT_INPUT: &str = "\
+#####
+.####
+.####
+.####
+.#.#.
+.#...
+.....
+
+#####
+##.##
+.#.##
+...##
+...#.
+...#.
+.....
+
+.....
+#....
+#....
+#...#
+#.#.#
+#.###
+#####
+
+.....
+.....
+#.#..
+###..
+###.#
+###.#
+#####
+
+.....
+.....
+.....
+#....
+#.#..
+#.#.#
+#####";
+
+ #[test]
+ fn test_part1() {
+ assert_eq!(3, run_part1(TEXT_INPUT).unwrap());
+ }
+
+ #[test]
+ fn test_part2() {
+ assert_eq!(0, run_part2(TEXT_INPUT).unwrap());
+ }
+}