diff --git a/2023/Cargo.toml b/2023/Cargo.toml index d44f029..890b19a 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -15,7 +15,7 @@ members = [ "day11", "day12", "day13", - # "day14", + "day14", # "day15", # "day16", # "day17", diff --git a/2023/day14/.gitignore b/2023/day14/.gitignore new file mode 100644 index 0000000..e983cca --- /dev/null +++ b/2023/day14/.gitignore @@ -0,0 +1,19 @@ +input.txt +flamegraph.svg +perf.data* +### Rust +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/2023/day14/Cargo.toml b/2023/day14/Cargo.toml new file mode 100644 index 0000000..79ace58 --- /dev/null +++ b/2023/day14/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day14" +authors = ["mirsella "] +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +grid = { git = "https://github.com/mirsella/grid" } diff --git a/2023/day14/build.rs b/2023/day14/build.rs new file mode 100644 index 0000000..b6a19a0 --- /dev/null +++ b/2023/day14/build.rs @@ -0,0 +1,28 @@ +use std::fs::File; +use std::io::{self, Read}; +use std::path::PathBuf; +use std::{env, fs}; + +fn replace_in_file(file_path: &PathBuf, old: &str, new: &str) -> io::Result<()> { + let mut contents = String::new(); + File::open(file_path)?.read_to_string(&mut contents)?; + let new_contents = contents.replace(old, new); + if contents != new_contents { + println!("Updating {}", file_path.display()); + fs::write(file_path, new_contents)?; + } + Ok(()) +} + +fn main() -> io::Result<()> { + let pkg_name = env::var("CARGO_PKG_NAME").unwrap(); + replace_in_file( + &"../Cargo.toml".into(), + &format!("# \"{pkg_name}\""), + &format!("\"{pkg_name}\""), + )?; + + replace_in_file(&"./Cargo.toml".into(), "\n[workspace]", "")?; + + Ok(()) +} diff --git a/2023/day14/src/main.rs b/2023/day14/src/main.rs new file mode 100644 index 0000000..5afa05f --- /dev/null +++ b/2023/day14/src/main.rs @@ -0,0 +1,103 @@ +use std::collections::HashMap; + +use grid::Grid; + +fn slide_up(col: &mut [&mut char]) { + let mut hard: isize = -1; + let mut last: isize = 0; + for i in 0..col.len() { + match col[i] { + '#' => { + hard = i as isize; + last = 0; + } + 'O' => { + *col[i] = '.'; + *col[(hard + last + 1) as usize] = 'O'; + last += 1; + } + _ => (), + } + } +} + +fn calculate_weight<'a>(col: impl Iterator, row_len: usize) -> usize { + col.enumerate().fold( + 0, + |acc, (i, &c)| { + if c == 'O' { + acc + (row_len - i) + } else { + acc + } + }, + ) +} + +fn part1(input: &str) -> usize { + let vec = input.chars().filter(|&c| c != '\n').collect::>(); + let mut grid = Grid::from_vec(vec, input.find('\n').unwrap()); + (0..grid.cols()) + .map(|i| { + let mut col = grid.iter_col_mut(i).collect::>(); + slide_up(col.as_mut_slice()); + calculate_weight(grid.iter_col(i), grid.cols()) + }) + .sum() +} +fn part2(input: &str, cycle: usize) -> usize { + let vec = input.chars().filter(|&c| c != '\n').collect::>(); + let mut grid = Grid::from_vec(vec, input.find('\n').unwrap()); + let mut map = HashMap::with_capacity(1000); + let mut count = 0; + let (spins, cycle_len) = loop { + count += 1; + for _ in 0..4 { + // FIX: 99% performance loss on this loop + (0..grid.cols()) + .for_each(|i| slide_up(grid.iter_col_mut(i).collect::>().as_mut_slice())); + grid.rotate_right(); + } + if let Some(dup) = map.insert(grid.iter().copied().collect::>(), count) { + break (count, count - dup); + } + }; + let spins_left = (cycle - spins) % cycle_len; + for _ in 0..spins_left { + for _ in 0..4 { + (0..grid.cols()) + .for_each(|i| slide_up(grid.iter_col_mut(i).collect::>().as_mut_slice())); + grid.rotate_right(); + } + } + (0..grid.cols()) + .map(|i| calculate_weight(grid.iter_col(i), grid.cols())) + .sum() +} +fn main() { + let input = include_str!("../input.txt"); + println!("Part 1: {}", part1(input)); + println!("Part 2: {}", part2(input, 1000000000)); +} + +#[cfg(test)] +mod tests { + const INPUT: &str = "O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#...."; + #[test] + fn part1() { + assert_eq!(super::part1(INPUT), 136); + } + #[test] + fn part2() { + assert_eq!(super::part2(INPUT, 1000000000), 64); + } +}