diff --git a/Cargo.lock b/Cargo.lock index 919a337..31a8806 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,10 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aoc_utils" +version = "0.1.0" + [[package]] name = "d01t1" version = "0.1.0" @@ -73,3 +77,14 @@ version = "0.1.0" [[package]] name = "d09t2" version = "0.1.0" + +[[package]] +name = "d10t1" +version = "0.1.0" +dependencies = [ + "aoc_utils", +] + +[[package]] +name = "d10t2" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 29a8e05..0606f18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ + "aoc_utils", "d01t1", "d01t2", "d02t1", @@ -20,4 +21,6 @@ members = [ "d08t2", "d09t1", "d09t2", + "d10t1", + "d10t2", ] diff --git a/aoc_utils/Cargo.toml b/aoc_utils/Cargo.toml new file mode 100644 index 0000000..51b4ff0 --- /dev/null +++ b/aoc_utils/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "aoc_utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/aoc_utils/src/lib.rs b/aoc_utils/src/lib.rs new file mode 100644 index 0000000..d9b9d44 --- /dev/null +++ b/aoc_utils/src/lib.rs @@ -0,0 +1,33 @@ +#[must_use] +pub fn dec_if_pos(input: usize) -> usize { + if input > 0 { + input - 1 + } else { + input + } +} + +#[must_use] +pub fn inc_if_lt(input: usize, max: usize) -> usize { + if input < max { + input + 1 + } else { + input + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn test_dec() { + assert_eq!(dec_if_pos(0), 0); + assert_eq!(dec_if_pos(1), 0); + } + #[test] + fn test_inc() { + assert_eq!(inc_if_lt(3, 3), 3); + assert_eq!(inc_if_lt(2, 3), 3); + } +} diff --git a/d10t1/Cargo.toml b/d10t1/Cargo.toml new file mode 100644 index 0000000..b535155 --- /dev/null +++ b/d10t1/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "d10t1" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aoc_utils = { path = "../aoc_utils" } diff --git a/d10t1/demo_input.txt b/d10t1/demo_input.txt new file mode 100644 index 0000000..3c00cf2 --- /dev/null +++ b/d10t1/demo_input.txt @@ -0,0 +1,5 @@ +..F7. +.FJ|. +SJ.L7 +|F--J +LJ... \ No newline at end of file diff --git a/d10t1/src/main.rs b/d10t1/src/main.rs new file mode 100644 index 0000000..ee68e4d --- /dev/null +++ b/d10t1/src/main.rs @@ -0,0 +1,94 @@ +use std::fs::read_to_string; + +fn main() { + let input = read_to_string("input.txt").unwrap(); + println!("d10t1: {}", d10t1(&input)); +} + +pub fn d10t1(input: &str) -> isize { + let field = input + .lines() + .map(str::chars) + .map(std::iter::Iterator::collect::>) + .collect::>(); + let mut distances = vec![vec![-1; field[0].len()]; field.len()]; + let mut starting_position: Option<(usize, usize)> = None; + for (y, line) in field.iter().enumerate() { + for (x, field) in line.iter().enumerate() { + if *field == 'S' { + starting_position = Some((x, y)); + distances[y][x] = 0; + } + } + } + let starting_position = starting_position.unwrap(); + traverse(&mut distances, &field, starting_position); + *distances.iter().flat_map(|a| a.iter()).max().unwrap() +} + +fn traverse(distances: &mut [Vec], field: &Vec>, position: (usize, usize)) { + let height = field.len() - 1; + let width = field[0].len() - 1; + let mut locations_to_check = vec![position]; + while let Some((x, y)) = locations_to_check.pop() { + let starting_value = distances[y][x]; + assert_ne!( + starting_value, -1, + "traverse should only be called on locations that have already been set." + ); + if x != 0 && (distances[y][x - 1] == -1 || distances[y][x - 1] > starting_value) { + match (field[y][x], field[y][x - 1]) { + ('|' | 'L' | 'F', _) | (_, '.' | '|' | 'J' | '7') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('-' | 'J' | '7' | 'S', '-' | 'L' | 'F') => { + distances[y][x - 1] = starting_value + 1; + locations_to_check.push((x - 1, y)); + } + _ => unimplemented!(), + } + } + if x < width && (distances[y][x + 1] == -1 || distances[y][x + 1] > starting_value) { + match (field[y][x], field[y][x + 1]) { + ('|' | 'J' | '7', _) | (_, '.' | '|' | 'L' | 'F') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('-' | 'L' | 'F' | 'S', '-' | 'J' | '7') => { + distances[y][x + 1] = starting_value + 1; + locations_to_check.push((x + 1, y)); + } + _ => unimplemented!(), + } + } + if y != 0 && (distances[y - 1][x] == -1 || distances[y - 1][x] > starting_value) { + match (field[y][x], field[y - 1][x]) { + ('-' | 'F' | '7', _) | (_, '.' | '-' | 'J' | 'L') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('|' | 'L' | 'J' | 'S', '|' | 'F' | '7') => { + distances[y - 1][x] = starting_value + 1; + locations_to_check.push((x, y - 1)); + } + _ => unimplemented!(), + } + } + if y < height && (distances[y + 1][x] == -1 || distances[y + 1][x] > starting_value) { + match (field[y][x], field[y + 1][x]) { + ('-' | 'L' | 'J', _) | (_, '.' | '-' | '7' | 'F') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('|' | 'F' | '7' | 'S', '|' | 'L' | 'J') => { + distances[y + 1][x] = starting_value + 1; + locations_to_check.push((x, y + 1)); + } + _ => unimplemented!(), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_d10t1() { + let input = include_str!("../demo_input.txt"); + assert_eq!(d10t1(input), 8); + } +} diff --git a/d10t2/Cargo.toml b/d10t2/Cargo.toml new file mode 100644 index 0000000..4af3ee2 --- /dev/null +++ b/d10t2/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "d10t2" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/d10t2/demo_input.txt b/d10t2/demo_input.txt new file mode 100644 index 0000000..fbc0300 --- /dev/null +++ b/d10t2/demo_input.txt @@ -0,0 +1,10 @@ +FF7FSF7F7F7F7F7F---7 +L|LJ||||||||||||F--J +FL-7LJLJ||||||LJL-77 +F--JF--7||LJLJ7F7FJ- +L---JF-JLJ.||-FJLJJ7 +|F|F-JF---7F7-L7L|7| +|FFJF7L7F-JF7|JL---7 +7-L-JL7||F7|L7F-7F7| +L.L7LFJ|||||FJL7||LJ +L7JLJL-JLJLJL--JLJ.L \ No newline at end of file diff --git a/d10t2/demo_input_2.txt b/d10t2/demo_input_2.txt new file mode 100644 index 0000000..2e5dcbb --- /dev/null +++ b/d10t2/demo_input_2.txt @@ -0,0 +1,10 @@ +.F----7F7F7F7F-7.... +.|F--7||||||||FJ.... +.||.FJ||||||||L7.... +FJL7L7LJLJ||LJ.L-7.. +L--J.L7...LJS7F-7L7. +....F-J..F7FJ|L7L7L7 +....L7.F7||L7|.L7L7| +.....|FJLJ|FJ|F7|.LJ +....FJL-7.||.||||... +....L---J.LJ.LJLJ... \ No newline at end of file diff --git a/d10t2/demo_input_3.txt b/d10t2/demo_input_3.txt new file mode 100644 index 0000000..6933a28 --- /dev/null +++ b/d10t2/demo_input_3.txt @@ -0,0 +1,9 @@ +........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +........... \ No newline at end of file diff --git a/d10t2/src/main.rs b/d10t2/src/main.rs new file mode 100644 index 0000000..69cf0a5 --- /dev/null +++ b/d10t2/src/main.rs @@ -0,0 +1,191 @@ +use std::fs::read_to_string; + +fn main() { + let input = read_to_string("input.txt").unwrap(); + println!("d10t2: {}", d10t2(&input)); +} + +pub fn d10t2(input: &str) -> usize { + let mut field = input + .lines() + .map(str::chars) + .map(std::iter::Iterator::collect::>) + .collect::>(); + let mut distances = vec![vec![-1; field[0].len()]; field.len()]; + let starting_position = field + .iter() + .enumerate() + .flat_map(|(y, line)| line.iter().enumerate().map(move |(x, c)| (x, y, c))) + .find(|&(_x, _y, c)| *c == 'S') + .map(|(x, y, _c)| (x, y)) + .unwrap(); + distances[starting_position.1][starting_position.0] = 0; + traverse(&mut distances, &mut field, starting_position); + get_enclosed(&distances, &field) +} + +fn get_effective_char_for_start(distances: &[Vec]) -> char { + let starting_position = distances + .iter() + .enumerate() + .flat_map(|(y, line)| line.iter().enumerate().map(move |(x, c)| (x, y, c))) + .find(|&(_x, _y, c)| *c == 0) + .map(|(x, y, _c)| (x, y)) + .unwrap(); + let bounds = (distances[0].len() - 1, distances.len() - 1); + let (x, y) = starting_position; + let top = if y == 0 { + false + } else { + distances[y - 1][x] == 1 + }; + let bottom = if y == bounds.1 { + false + } else { + distances[y + 1][x] == 1 + }; + let left = if x == 0 { + false + } else { + distances[y][x - 1] == 1 + }; + let right = if x == bounds.0 { + false + } else { + distances[y][x + 1] == 1 + }; + match (top, bottom, left, right) { + (true, true, false, false) => '|', + (false, false, true, true) => '-', + (true, false, true, false) => 'J', + (true, false, false, true) => 'L', + (false, true, true, false) => '7', + (false, true, false, true) => 'F', + _ => unreachable!(), + } +} + +fn get_enclosed(distances: &[Vec], field: &[Vec]) -> usize { + let mut result = 0; + let s_char = get_effective_char_for_start(distances); + let unique_y_changes = |x: usize, until: usize| -> usize { + field[0..until] + .iter() + .enumerate() + .filter(|(idx, _)| distances[*idx][x] != -1) + .map(|(_, s)| s[x]) + .collect::() + .replace('S', s_char.to_string().as_str()) + .replace('|', "") + .replace("FL", "") + .replace("FJ", "-") + .replace("7L", "-") + .replace("7J", "") + .replace('.', "") + .len() + }; + let unique_x_changes = |y: usize, until: usize| -> usize { + field[y][0..until] + .iter() + .enumerate() + .filter(|(idx, _)| distances[y][*idx] != -1) + .map(|(_, c)| *c) + .collect::() + .replace('S', s_char.to_string().as_str()) + .replace('-', "") + .replace("F7", "") + .replace("FJ", "|") + .replace("L7", "|") + .replace("LJ", "") + .replace('.', "") + .len() + }; + for (y, line) in distances.iter().enumerate() { + for (x, _d) in line.iter().enumerate() { + if distances[y][x] == -1 { + if unique_x_changes(y, x) % 2 == 1 && unique_y_changes(x, y) % 2 == 1 { + result += 1; + } + } + } + } + + result +} + +fn traverse(distances: &mut [Vec], field: &mut Vec>, position: (usize, usize)) { + let height = field.len() - 1; + let width = field[0].len() - 1; + let mut locations_to_check = vec![position]; + while let Some((x, y)) = locations_to_check.pop() { + let starting_value = distances[y][x]; + assert_ne!( + starting_value, -1, + "traverse should only be called on locations that have already been set." + ); + if x != 0 && (distances[y][x - 1] == -1 || distances[y][x - 1] > starting_value) { + match (field[y][x], field[y][x - 1]) { + ('|' | 'L' | 'F', _) | (_, '.' | '|' | 'J' | '7') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('-' | 'J' | '7' | 'S', '-' | 'L' | 'F') => { + distances[y][x - 1] = starting_value + 1; + locations_to_check.push((x - 1, y)); + } + _ => unimplemented!(), + } + } + if x < width && (distances[y][x + 1] == -1 || distances[y][x + 1] > starting_value) { + match (field[y][x], field[y][x + 1]) { + ('|' | 'J' | '7', _) | (_, '.' | '|' | 'L' | 'F') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('-' | 'L' | 'F' | 'S', '-' | 'J' | '7') => { + distances[y][x + 1] = starting_value + 1; + locations_to_check.push((x + 1, y)); + } + _ => unimplemented!(), + } + } + if y != 0 && (distances[y - 1][x] == -1 || distances[y - 1][x] > starting_value) { + match (field[y][x], field[y - 1][x]) { + ('-' | 'F' | '7', _) | (_, '.' | '-' | 'J' | 'L') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('|' | 'L' | 'J' | 'S', '|' | 'F' | '7') => { + distances[y - 1][x] = starting_value + 1; + locations_to_check.push((x, y - 1)); + } + _ => unimplemented!(), + } + } + if y < height && (distances[y + 1][x] == -1 || distances[y + 1][x] > starting_value) { + match (field[y][x], field[y + 1][x]) { + ('-' | 'L' | 'J', _) | (_, '.' | '-' | '7' | 'F') => {} + ('.', _) | (_, 'S') => unreachable!(), + ('|' | 'F' | '7' | 'S', '|' | 'L' | 'J') => { + distances[y + 1][x] = starting_value + 1; + locations_to_check.push((x, y + 1)); + } + _ => unimplemented!(), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_d10t2() { + let input = include_str!("../demo_input.txt"); + assert_eq!(d10t2(input), 10); + } + #[test] + fn test_d10t2_2() { + let input = include_str!("../demo_input_2.txt"); + assert_eq!(d10t2(input), 8); + } + #[test] + fn test_d10t2_3() { + let input = include_str!("../demo_input_3.txt"); + assert_eq!(d10t2(input), 4); + } +}