use hashbrown::HashSet; use itertools::Itertools; fn is_abba(address_part: &str) -> bool { for (a, b, c, d) in address_part.chars().tuple_windows() { if a == d && b == c && a != b { return true; } } false } fn get_abas(address_part: &str) -> Vec<(char, char)> { address_part .chars() .tuple_windows() .map(|(a, b, c)| (a, b, c)) .filter(|(a, b, c)| a == c && a != b) .map(|(a, b, _)| (a, b)) .collect_vec() } pub fn part_one(input: &str) -> Option { let mut count = 0; 'lines: for line in input.lines() { let mut found_abba = false; for (idx, address_part) in line.split(['[', ']']).enumerate() { match idx % 2 { 0 => found_abba |= is_abba(address_part), 1 => { if is_abba(address_part) { continue 'lines; } } _ => unreachable!(), } } if found_abba { count += 1; } } Some(count) } pub fn part_two(input: &str) -> Option { let mut count = 0; for line in input.lines() { let mut abas = HashSet::new(); let mut babs = HashSet::new(); for (idx, address_part) in line.split(['[', ']']).enumerate() { match idx % 2 { 0 => abas.extend(get_abas(address_part)), 1 => babs.extend(get_abas(address_part).into_iter().map(|(a, b)| (b, a))), _ => unreachable!(), } } if !abas.intersection(&babs).collect_vec().is_empty() { count += 1; } } Some(count) } fn main() { let input = &aoc::read_file("inputs", 7); aoc::solve!(1, part_one, input); aoc::solve!(2, part_two, input); } #[cfg(test)] mod tests { use super::*; #[test] fn test_part_one() { let input = aoc::read_file("examples", 7); assert_eq!(part_one(&input), Some(2)); } #[test] fn test_part_two() { let input = aoc::read_file("examples", 7); assert_eq!(part_two(&input), Some(3)); } }