diff --git a/Cargo.lock b/Cargo.lock index c7776f6..65e034a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,7 @@ dependencies = [ "aoc-runner-derive", "nom", "regex", + "topological-sort", ] [[package]] @@ -185,6 +186,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 18f5e65..70f6aa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ aoc-runner = "0.3.0" aoc-runner-derive = "0.3.0" nom = "7.1.3" regex = "1.11.1" +topological-sort = "0.2.2" diff --git a/rustfmt.toml b/rustfmt.toml index ffc467d..4194bf2 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1 @@ -max_width = 200 +# max_width = 1000 diff --git a/src/day4.rs b/src/day4.rs index 8c819e5..11badd1 100644 --- a/src/day4.rs +++ b/src/day4.rs @@ -1,21 +1,39 @@ use aoc_runner_derive::{aoc, aoc_generator}; #[aoc_generator(day4)] fn parse(input: &str) -> Vec> { - input.split("\n").map(|line| line.chars().collect()).collect() + input + .split("\n") + .map(|line| line.chars().collect()) + .collect() } #[aoc(day4, part1)] fn part1(d: &[Vec]) -> usize { - (0..d.len()) - .flat_map(|i| (0..d[0].len()).map(move |j| (j as i64, i as i64, j > 2, j < d.len() - 3, i > 2, i < d[0].len() - 3))) - .filter(|p| d[p.1 as usize][p.0 as usize] == 'X') - .flat_map(|p| { - (-1..=1) - .flat_map(|dx| (-1..=1).filter(move |i| dx != 0 || *i != 0).map(move |i| (dx, i))) - .filter(move |(a, b)| (*a == 0 || (*a == -1 && p.2) || (*a == 1 && p.3)) && (*b == 0 || (*b == -1 && p.4) || (*b == 1 && p.5))) - .filter(move |r| (1..=3).zip("MAS".chars()).all(|v| d[(p.1 + v.0 * r.1) as usize][(p.0 + v.0 * r.0) as usize] == v.1)) - }) - .count() + (0..d.len()).fold(0, |z, i| { + z + (0..d[0].len()) + .map(move |j| { + ( + j as i64, + i as i64, + j > 2, + j < d.len() - 3, + i > 2, + i < d[0].len() - 3, + ) + }) + .fold(0, |z, p| { + z + (-1..=1).fold(0, |z, f| { + z + (-1..=1).fold(0, move |z, g| { + z + ((f != 0 || g != 0) + && (f == 0 || (f == -1 && p.2) || (f == 1 && p.3)) + && (g == 0 || (g == -1 && p.4) || (g == 1 && p.5)) + && (0..=3).zip("XMAS".chars()).all(|v| { + d[(p.1 + v.0 * g) as usize][(p.0 + v.0 * f) as usize] == v.1 + })) as usize + }) + }) + }) + }) } fn _part1(input: &[Vec]) -> u64 { @@ -25,36 +43,72 @@ fn _part1(input: &[Vec]) -> u64 { for y in 0..h { for x in 0..w { if input[y][x] == 'S' { - if x < w - 3 && input[y][x + 1] == 'A' && input[y][x + 2] == 'M' && input[y][x + 3] == 'X' { + if x < w - 3 + && input[y][x + 1] == 'A' + && input[y][x + 2] == 'M' + && input[y][x + 3] == 'X' + { // println!("rev X {} {}", x, y); sum += 1 } - if y < h - 3 && input[y + 1][x] == 'A' && input[y + 2][x] == 'M' && input[y + 3][x] == 'X' { + if y < h - 3 + && input[y + 1][x] == 'A' + && input[y + 2][x] == 'M' + && input[y + 3][x] == 'X' + { // println!("rev Y {} {}", x, y); sum += 1 } - if x < w - 3 && y < h - 3 && input[y + 1][x + 1] == 'A' && input[y + 2][x + 2] == 'M' && input[y + 3][x + 3] == 'X' { + if x < w - 3 + && y < h - 3 + && input[y + 1][x + 1] == 'A' + && input[y + 2][x + 2] == 'M' + && input[y + 3][x + 3] == 'X' + { // println!("rdi Y {} {}", x, y); sum += 1 } - if x >= 3 && y < h - 3 && input[y + 1][x - 1] == 'A' && input[y + 2][x - 2] == 'M' && input[y + 3][x - 3] == 'X' { + if x >= 3 + && y < h - 3 + && input[y + 1][x - 1] == 'A' + && input[y + 2][x - 2] == 'M' + && input[y + 3][x - 3] == 'X' + { // println!("rdi Y {} {}", x, y); sum += 1 } } else if input[y][x] == 'X' { - if x < w - 3 && input[y][x + 1] == 'M' && input[y][x + 2] == 'A' && input[y][x + 3] == 'S' { + if x < w - 3 + && input[y][x + 1] == 'M' + && input[y][x + 2] == 'A' + && input[y][x + 3] == 'S' + { // println!(" X {} {}", x, y); sum += 1 } - if y < h - 3 && input[y + 1][x] == 'M' && input[y + 2][x] == 'A' && input[y + 3][x] == 'S' { + if y < h - 3 + && input[y + 1][x] == 'M' + && input[y + 2][x] == 'A' + && input[y + 3][x] == 'S' + { // println!(" Y {} {}", x, y); sum += 1 } - if x < w - 3 && y < h - 3 && input[y + 1][x + 1] == 'M' && input[y + 2][x + 2] == 'A' && input[y + 3][x + 3] == 'S' { + if x < w - 3 + && y < h - 3 + && input[y + 1][x + 1] == 'M' + && input[y + 2][x + 2] == 'A' + && input[y + 3][x + 3] == 'S' + { // println!("dia Y {} {}", x, y); sum += 1 } - if x >= 3 && y < h - 3 && input[y + 1][x - 1] == 'M' && input[y + 2][x - 2] == 'A' && input[y + 3][x - 3] == 'S' { + if x >= 3 + && y < h - 3 + && input[y + 1][x - 1] == 'M' + && input[y + 2][x - 2] == 'A' + && input[y + 3][x - 3] == 'S' + { // println!("dia Y {} {}", x, y); sum += 1 } @@ -72,8 +126,10 @@ fn part2(input: &[Vec]) -> u64 { for y in 1..h - 1 { for x in 1..w - 1 { if input[y][x] == 'A' { - let diag1 = (input[y - 1][x - 1] == 'M' && input[y + 1][x + 1] == 'S') || (input[y - 1][x - 1] == 'S' && input[y + 1][x + 1] == 'M'); - let diag2 = (input[y - 1][x + 1] == 'M' && input[y + 1][x - 1] == 'S') || (input[y - 1][x + 1] == 'S' && input[y + 1][x - 1] == 'M'); + let diag1 = (input[y - 1][x - 1] == 'M' && input[y + 1][x + 1] == 'S') + || (input[y - 1][x - 1] == 'S' && input[y + 1][x + 1] == 'M'); + let diag2 = (input[y - 1][x + 1] == 'M' && input[y + 1][x - 1] == 'S') + || (input[y - 1][x + 1] == 'S' && input[y + 1][x - 1] == 'M'); if diag1 && diag2 { sum += 1; } @@ -89,22 +145,12 @@ mod tests { #[test] fn part1_example() { - assert_eq!( - part1(&parse( - "MMMSXXMASM\nMSAMXMSMSA\nAMXSXMAAMM\nMSAMASMSMX\nXMASAMXAMM\nXXAMMXXAMA\nSMSMSASXSS\nSAXAMASAAA\nMAMMMXMMMM\nMXMXAXMASX" - )), - 18 - ); + assert_eq!(part1(&parse("MMMSXXMASM\nMSAMXMSMSA\nAMXSXMAAMM\nMSAMASMSMX\nXMASAMXAMM\nXXAMMXXAMA\nSMSMSASXSS\nSAXAMASAAA\nMAMMMXMMMM\nMXMXAXMASX")), 18); assert_eq!(part1(&parse(include_str!("../input/2024/day4.txt"))), 2406); } #[test] fn part2_example() { - assert_eq!( - part2(&parse( - ".M.S......\n..A..MSMS.\n.M.S.MAA..\n..A.ASMSM.\n.M.S.M....\n..........\nS.S.S.S.S.\n.A.A.A.A..\nM.M.M.M.M.\n.........." - )), - 9 - ); + assert_eq!(part2(&parse(".M.S......\n..A..MSMS.\n.M.S.MAA..\n..A.ASMSM.\n.M.S.M....\n..........\nS.S.S.S.S.\n.A.A.A.A..\nM.M.M.M.M.\n..........")), 9); } } diff --git a/src/day5.rs b/src/day5.rs new file mode 100644 index 0000000..8df3f02 --- /dev/null +++ b/src/day5.rs @@ -0,0 +1,125 @@ +use std::collections::HashMap; + +use aoc_runner_derive::{aoc, aoc_generator}; +use nom::character::complete::u64 as parse_u64; +use nom::multi::separated_list1; +use nom::IResult; +use nom::{ + bytes::complete::tag, character::complete::newline, multi::separated_list0, + sequence::separated_pair, +}; +use topological_sort::TopologicalSort; + +type Input = (Vec<(u64, u64)>, Vec>); + +#[aoc_generator(day5)] +fn parse(input: &str) -> Input { + parse_lines(input).unwrap().1 +} + +fn parse_lines(input: &str) -> IResult<&str, Input> { + let (input, rules) = + separated_list0(newline, separated_pair(parse_u64, tag("|"), parse_u64))(input)?; + let (input, _) = newline(input)?; + let (input, _) = newline(input)?; + let (input, records) = separated_list0(newline, separated_list1(tag(","), parse_u64))(input)?; + Ok((input, (rules, records))) +} + +#[aoc(day5, part1)] +fn part1(input: &Input) -> u64 { + let rules = input.0.clone(); + let records = input.1.clone(); + + let mut rules_map: HashMap> = HashMap::new(); + rules.iter().for_each(|rule| { + if let std::collections::hash_map::Entry::Vacant(e) = rules_map.entry(rule.1) { + e.insert(vec![rule.0]); + } else { + rules_map.get_mut(&rule.1).unwrap().push(rule.0); + rules_map.get_mut(&rule.1).unwrap().sort(); + } + }); + + let mut correct = 0; + records.iter().for_each(|record| { + let mut forbidden = vec![]; + let valid = record.iter().all(|page| { + let allowed = !forbidden.contains(page); + if rules_map.contains_key(page) { + let mut new_forbidden = rules_map.get(page).unwrap().clone(); + forbidden.append(&mut new_forbidden); + } + allowed + }); + if valid { + correct += record[record.len() >> 1] + } + }); + + correct +} + +#[aoc(day5, part2)] +fn part2(input: &Input) -> u64 { + let rules = input.0.clone(); + let records = input.1.clone(); + + let mut rules_map: HashMap> = HashMap::new(); + rules.iter().for_each(|rule| { + if let std::collections::hash_map::Entry::Vacant(e) = rules_map.entry(rule.1) { + e.insert(vec![rule.0]); + } else { + rules_map.get_mut(&rule.1).unwrap().push(rule.0); + rules_map.get_mut(&rule.1).unwrap().sort(); + } + }); + + let mut correct = 0; + records.iter().for_each(|record| { + let mut forbidden = vec![]; + let valid = record.iter().all(|page| { + let allowed = !forbidden.contains(page); + if rules_map.contains_key(page) { + let mut new_forbidden = rules_map.get(page).unwrap().clone(); + forbidden.append(&mut new_forbidden); + } + allowed + }); + if !valid { + // dbg!(&record); + let mut ts = TopologicalSort::::new(); + for i in record { + ts.insert(*i); + if let Some(rules) = rules_map.get(i) { + for rule in rules { + ts.add_dependency(*rule, *i); + } + } + } + + let sorted = ts + .clone() + .filter(|a| record.contains(a)) + .collect::>(); + correct += sorted[sorted.len() >> 1] + } + }); + + correct +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + assert_eq!(part1(&parse("47|53\n97|13\n97|61\n97|47\n75|29\n61|13\n75|53\n29|13\n97|29\n53|29\n61|53\n97|53\n61|29\n47|13\n75|47\n97|75\n47|61\n75|61\n47|29\n75|13\n53|13\n\n75,47,61,53,29\n97,61,53,29,13\n75,29,13\n75,97,47,61,53\n61,13,29\n97,13,75,29,47")), 143); + } + + #[test] + fn part2_example() { + assert_eq!(part2(&parse("47|53\n97|13\n97|61\n97|47\n75|29\n61|13\n75|53\n29|13\n97|29\n53|29\n61|53\n97|53\n61|29\n47|13\n75|47\n97|75\n47|61\n75|61\n47|29\n75|13\n53|13\n\n75,47,61,53,29\n97,61,53,29,13\n75,29,13\n75,97,47,61,53\n61,13,29\n97,13,75,29,47")), 123); + } +} diff --git a/src/day6.rs b/src/day6.rs new file mode 100644 index 0000000..bf990b1 --- /dev/null +++ b/src/day6.rs @@ -0,0 +1,173 @@ +type Input = ((usize, usize), Vec>); + +use aoc_runner_derive::{aoc, aoc_generator}; +#[aoc_generator(day6)] +fn parse(input: &str) -> Input { + let lines = input.split("\n"); + let mut pos = (0, 0); + let map = lines + .enumerate() + .map(|(y, line)| { + line.chars() + .enumerate() + .map(|(x, c)| match c { + '.' => 0, + '#' => 1, + '^' => { + pos = (x, y); + 0 + } + _ => todo!(), + }) + .collect() + }) + .collect(); + (pos, map) +} + +#[aoc(day6, part1)] +fn part1(input: &Input) -> u64 { + let h = input.1.len(); + let w = input.1[0].len(); + let mut map = input.1.clone(); + let mut pos = input.0; + let mut dir = (0, -1); + let mut count = 0; + loop { + if map[pos.1][pos.0] == 0 { + map[pos.1][pos.0] = 2; + count += 1; + }; + if pos.0 as isize + dir.0 < 0 + || pos.0 as isize + dir.0 >= w as isize + || pos.1 as isize + dir.1 < 0 + || pos.1 as isize + dir.1 >= h as isize + { + break; + } + while map[(pos.1 as isize + dir.1) as usize][(pos.0 as isize + dir.0) as usize] == 1 { + if dir.0 == 1 { + dir = (0, 1); + } else if dir.0 == -1 { + dir = (0, -1); + } else if dir.1 == 1 { + dir = (-1, 0); + } else if dir.1 == -1 { + dir = (1, 0); + } + } + pos = ( + (pos.0 as isize + dir.0) as usize, + (pos.1 as isize + dir.1) as usize, + ) + } + count +} + +#[aoc(day6, part2)] +fn part2(input: &Input) -> u64 { + let initial = input.0; + let h = input.1.len(); + let w = input.1[0].len(); + let mut map = input.1.clone(); + let mut pos = input.0; + let mut dir = (0, -1); + let mut loops = vec![]; + loop { + if pos.0 as isize + dir.0 < 0 + || pos.0 as isize + dir.0 >= w as isize + || pos.1 as isize + dir.1 < 0 + || pos.1 as isize + dir.1 >= h as isize + { + break; + } + while map[(pos.1 as isize + dir.1) as usize][(pos.0 as isize + dir.0) as usize] == 1 { + if dir.0 == 1 { + dir = (0, 1); + } else if dir.0 == -1 { + dir = (0, -1); + } else if dir.1 == 1 { + dir = (-1, 0); + } else if dir.1 == -1 { + dir = (1, 0); + } + } + let next_pos = ( + (pos.0 as isize + dir.0) as usize, + (pos.1 as isize + dir.1) as usize, + ); + if map[next_pos.1][next_pos.0] != 1 { + map[next_pos.1][next_pos.0] = 1; + if check_loop(initial, (0, -1), &map) { + // count += 1; + if !(loops.contains(&next_pos)) && next_pos != initial { + loops.push(next_pos); + } + // println!("{} {}", nexts_pos.0 + 1, next_pos.1 + 1); + } + map[next_pos.1][next_pos.0] = 0; + } + pos = next_pos; + } + loops.len() as u64 +} + +fn check_loop(pos: (usize, usize), dir: (isize, isize), map: &[Vec]) -> bool { + let mut visited = vec![]; + + let h = map.len(); + let w = map[0].len(); + + let mut dir = dir; + let mut pos = pos; + + loop { + if pos.0 as isize + dir.0 < 0 + || pos.0 as isize + dir.0 >= w as isize + || pos.1 as isize + dir.1 < 0 + || pos.1 as isize + dir.1 >= h as isize + { + break; + } + let mut turns = 0; + while map[(pos.1 as isize + dir.1) as usize][(pos.0 as isize + dir.0) as usize] == 1 { + if dir.0 == 1 { + dir = (0, 1); + } else if dir.0 == -1 { + dir = (0, -1); + } else if dir.1 == 1 { + dir = (-1, 0); + } else if dir.1 == -1 { + dir = (1, 0); + } + + turns += 1; + } + pos = ( + (pos.0 as isize + dir.0) as usize, + (pos.1 as isize + dir.1) as usize, + ); + if visited.contains(&(pos.0, pos.1, dir.0, dir.1)) { + return true; + } else if turns > 0 { + visited.push((pos.0, pos.1, dir.0, dir.1)); + } + } + + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + assert_eq!(part1(&parse("....#.....\n.........#\n..........\n..#.......\n.......#..\n..........\n.#..^.....\n........#.\n#.........\n......#...")), 41); + } + + #[test] + fn part2_example() { + assert_eq!(part2(&parse("....#.....\n.........#\n..........\n..#.......\n.......#..\n..........\n.#..^.....\n........#.\n#.........\n......#...")), 6); + } +} diff --git a/src/lib.rs b/src/lib.rs index 80a5a1d..23ec5de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +mod day6; +mod day5; mod day4; mod day1; mod day2;