use std::collections::HashSet; const INPUT: &str = include_str!("../data/day3.txt"); pub fn day3a() -> String { format!("{}", sum_parts(INPUT.to_owned())) } pub fn day3b() -> String { format!( "{}", find_gears(INPUT.to_owned()) .into_iter() .map(|gear| gear.ratio()) .fold(0, |acc, val| acc + val) ) } #[derive(Clone, Debug, PartialEq)] struct Addr { row: usize, start: usize, end: usize, } impl Addr { fn span(&self) -> Vec<(usize, usize)> { (self.start..self.end + 1) .map(|addr| (self.row, addr)) .collect() } } #[derive(Clone, Debug, PartialEq)] struct Gear(u32, u32); impl Gear { fn ratio(&self) -> u32 { self.0 * self.1 } } fn sum_parts(schematic: String) -> u32 { parts(schematic).into_iter().fold(0, |acc, val| acc + val.0) } fn parts(schematic: String) -> Vec<(u32, Addr)> { let sc = schematic .lines() .map(|l| l.to_owned()) .collect::>(); let numbers = find_numbers(schematic.clone()); numbers .into_iter() .filter(|number| is_part(&sc, number)) .collect::>() } fn find_gears(schematic: String) -> Vec { let sc = schematic .lines() .map(|l| l.to_owned()) .collect::>(); let max_row = sc.len(); let max_col = sc[0].len(); let parts = parts(schematic); let splats = find_splats(sc) .into_iter() .map(|(row, col)| { halo( Addr { row, start: col, end: col, }, max_row - 1, max_col - 1, ) }) .collect::>>(); let mut gears = vec![]; for splat in splats { let overlap = parts .iter() .filter(|part| part_within_halo(splat.clone(), part.1.clone())) .map(|part| part.0) .collect::>(); if overlap.len() == 2 { gears.push(Gear(overlap[0], overlap[1])); } } gears } fn part_within_halo(halo1: Vec<(usize, usize)>, part: Addr) -> bool { let halo1: HashSet<(usize, usize)> = halo1.into_iter().collect::>(); let halo2: HashSet<(usize, usize)> = part.span().into_iter().collect::>(); halo1.intersection(&halo2).count() > 0 } fn is_part(schematic: &Vec, number: &(u32, Addr)) -> bool { halo( number.1.clone(), schematic.len() - 1, schematic[0].len() - 1, ) .into_iter() .any(|(row, col)| { let cell = schematic[row].chars().nth(col).unwrap(); !cell.is_digit(10) && cell != '.' }) } fn halo(addr: Addr, max_row: usize, max_col: usize) -> Vec<(usize, usize)> { let mut halo: Vec<(i32, i32)> = vec![]; for r in addr.start..addr.end + 1 { halo.push((addr.row as i32 - 1, r as i32)); halo.push((addr.row as i32 + 1, r as i32)); } halo.push((addr.row as i32 - 1, addr.start as i32 - 1)); halo.push((addr.row as i32, addr.start as i32 - 1)); halo.push((addr.row as i32 + 1, addr.start as i32 - 1)); halo.push((addr.row as i32 - 1, addr.end as i32 + 1)); halo.push((addr.row as i32, addr.end as i32 + 1)); halo.push((addr.row as i32 + 1, addr.end as i32 + 1)); halo.into_iter() .filter(|(row, col)| { *row >= 0 && *row <= max_row as i32 && *col >= 0 && *col <= max_col as i32 }) .map(|(row, col)| (row as usize, col as usize)) .collect() } fn find_numbers(schematic: String) -> Vec<(u32, Addr)> { let mut result = vec![]; for (idx, line) in schematic.lines().enumerate() { let mut numbers = find_numbers_in_line(line) .into_iter() .map(|(number, start, len)| { ( number, Addr { row: idx, start, end: start + len - 1, }, ) }) .collect::>(); result.append(&mut numbers); } result } fn find_numbers_in_line(line: &str) -> Vec<(u32, usize, usize)> { let mut numbers = vec![]; let mut start_idx: usize = 0; let mut number: Option = None; for idx in 0..line.len() { match (number.take(), line.chars().nth(idx).unwrap()) { (None, c) if c.is_digit(10) => { start_idx = idx; number = Some(String::from(c)); } (Some(mut num), c) if c.is_digit(10) => { num.push(c); number = Some(num); } (Some(num), _) => { numbers.push((num, start_idx)); number = None; } (_, _) => {} } } if let Some(num) = number { numbers.push((num, start_idx)); } numbers .into_iter() .map(|(number, start_idx)| (number.parse::().unwrap(), start_idx, number.len())) .collect() } fn find_splats(schematic: Vec) -> Vec<(usize, usize)> { schematic .into_iter() .enumerate() .map(|(row_idx, row)| { row.chars() .enumerate() .filter_map(|(col_idx, c)| { if is_splat(c) { Some((row_idx, col_idx)) } else { None } }) .collect::>() }) .flatten() .collect::>() } fn is_symbol(c: char) -> bool { !c.is_digit(10) && c != '.' } fn is_splat(c: char) -> bool { c == '*' } #[cfg(test)] mod test { use super::*; const INPUT: &str = "467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.."; #[test] fn part_1() { assert_eq!( parts(INPUT.to_owned()) .into_iter() .map(|part| part.0) .collect::>(), vec![467, 35, 633, 617, 592, 755, 664, 598] ); assert_eq!(sum_parts(INPUT.to_owned()), 4361); } #[test] fn test_find_numbers() { println!("{:?}", find_numbers(INPUT.to_owned())); assert_eq!( find_numbers(INPUT.to_owned()), vec![ ( 467, Addr { row: 0, start: 0, end: 2 } ), ( 114, Addr { row: 0, start: 5, end: 7 } ), ( 35, Addr { row: 2, start: 2, end: 3 } ), ( 633, Addr { row: 2, start: 6, end: 8 } ), ( 617, Addr { row: 4, start: 0, end: 2 } ), ( 58, Addr { row: 5, start: 7, end: 8, } ), ( 592, Addr { row: 6, start: 2, end: 4, } ), ( 755, Addr { row: 7, start: 6, end: 8, } ), ( 664, Addr { row: 9, start: 1, end: 3, } ), ( 598, Addr { row: 9, start: 5, end: 7, } ) ] ); } #[test] fn test_find_numbers_in_line() { let test_cases = vec![ ("...*......", vec![]), (".664.598..", vec![(664, 1, 3), (598, 5, 3)]), ("..35..633.", vec![(35, 2, 2), (633, 6, 3)]), ( "......883.....*163...782", vec![(883, 6, 3), (163, 15, 3), (782, 21, 3)], ), ]; for (input, expected) in test_cases { assert_eq!(find_numbers_in_line(input), expected); } } #[test] fn test_halo() { let schematic = INPUT.lines().map(|l| l.to_owned()).collect::>(); let number = Addr { row: 0, start: 0, end: 2, }; assert_eq!( halo(number, schematic.len() - 1, schematic[0].len() - 1), vec![(1, 0), (1, 1), (1, 2), (0, 3), (1, 3)] ); assert_eq!( halo( Addr { row: 1, start: 1, end: 1 }, schematic.len() - 1, schematic[0].len() - 1 ), vec![ (0, 1), (2, 1), (0, 0), (1, 0), (2, 0), (0, 2), (1, 2), (2, 2) ] ); assert_eq!( halo( Addr { row: 6, start: 2, end: 4, }, schematic.len() - 1, schematic[0].len() - 1 ), vec![ (5, 2), (7, 2), (5, 3), (7, 3), (5, 4), (7, 4), (5, 1), (6, 1), (7, 1), (5, 5), (6, 5), (7, 5), ] ); } #[test] fn test_find_gears() { let gears = find_gears(INPUT.to_owned()); assert_eq!(gears, vec![Gear(467, 35), Gear(755, 598)]); assert_eq!( gears.into_iter().map(|g| g.ratio()).collect::>(), vec![16345, 451490] ); } }