From 14c1e412eb1fd42de0569d3e63bf9106893e7f0d Mon Sep 17 00:00:00 2001 From: shoofle Date: Thu, 12 Dec 2024 10:40:37 -0500 Subject: [PATCH] days 9 10 11 and 12 --- day09/Cargo.toml | 6 + day09/src/main.rs | 345 ++++++++++++++++++++++++++++++++++++++++++++++ day10/Cargo.toml | 6 + day10/src/main.rs | 124 +++++++++++++++++ day11/Cargo.toml | 7 + day11/src/main.rs | 69 ++++++++++ day12/Cargo.toml | 6 + day12/src/main.rs | 252 +++++++++++++++++++++++++++++++++ 8 files changed, 815 insertions(+) create mode 100644 day09/Cargo.toml create mode 100644 day09/src/main.rs create mode 100644 day10/Cargo.toml create mode 100644 day10/src/main.rs create mode 100644 day11/Cargo.toml create mode 100644 day11/src/main.rs create mode 100644 day12/Cargo.toml create mode 100644 day12/src/main.rs diff --git a/day09/Cargo.toml b/day09/Cargo.toml new file mode 100644 index 0000000..6243883 --- /dev/null +++ b/day09/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day09" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day09/src/main.rs b/day09/src/main.rs new file mode 100644 index 0000000..d2c5035 --- /dev/null +++ b/day09/src/main.rs @@ -0,0 +1,345 @@ +use std::collections::HashMap; +use std::fs; +use std::env; + +#[derive(Clone, Copy, Debug)] +enum Record { + Gap { start: i32, length: i32}, + File { start: i32, length: i32, file: i32 } +} + +struct Drive { + in_order: Vec, + files: HashMap, +} +fn main() { + println!("Hello, AoC day 09!"); + + let args: Vec = env::args().collect(); + if args.len() != 2 { + println!("wrong number of arguments!"); + std::process::exit(1); + } + + let file_path = &args[1]; + + let contents = fs::read_to_string(file_path).expect("Should have been able to read the file"); + + let mut drive = read_drive(&contents); + print_drive(&drive); + compress(&mut drive); + print_drive(&drive); + let sum = checksum(&drive); + println!("checksum post-compaction is {sum}."); + + let mut drive = read_drive_enum(&contents); + print_enums(&drive); + drive = compress_enums(drive); + print_enums(&drive); + let sum = checksum_enum(&drive); + println!("checksum post-compression is {sum}") +} + +fn read_drive(contents: &str) -> Vec { + let mut files: i32 = 0; + let mut empty: i32 = 0; + let mut is_file = true; + for character in contents.chars() { + let maybe = character.to_string().parse::(); + if maybe.is_err() { + continue; + } + let num = maybe.unwrap(); + if is_file { + files += num; + } else { + empty += num; + } + is_file = !is_file; + } + + let mut drive: Vec = vec![-1; (empty+files).try_into().unwrap()]; + let mut file_number: i32 = 0; + let mut index: usize = 0; + let mut is_file = true; + for character in contents.chars() { + let maybe = character.to_string().parse::(); + if maybe.is_err() { + continue; + } + let num = maybe.unwrap(); + if is_file { + for _i in 0..num { + drive[index] = file_number; + index += 1; + } + file_number += 1; + } else { + index += num as usize; + } + is_file = !is_file; + } + + return drive; +} + +fn print_drive(drive: &[i32]) { + for &x in drive { + if x == -1 { + print!("."); + } else { + print!("{}", x); + } + } + print!("\n"); +} + +fn checksum(drive: &[i32]) -> i64 { + let mut sum: i64 = 0; + for index in 0..drive.len() { + if drive[index] == -1 { continue; } + sum += (drive[index] as i64) * (index as i64); + } + + return sum; +} + +fn compress(drive: &mut Vec) { + let mut write_index: usize = 0; + for read_index in (0..drive.len()).rev() { + if write_index == drive.len() { break; } + + let file = drive[read_index]; + if file == -1 { continue; } + + while drive[write_index] != -1 { + write_index += 1; + } + if write_index >= read_index { break; } + + drive[write_index] = file; + drive[read_index] = -1; + write_index += 1; + } +} + +fn read_drive_enum(contents: &str) -> Drive { + let mut drive: Vec = Vec::with_capacity(contents.len()); + let mut files: HashMap = HashMap::new(); + + let mut is_file = true; + let mut file_number = 0; + let mut index = 0; + for c in contents.trim().chars() { + let num = c.to_string().parse::().unwrap(); + if is_file { + let file = Record::File { + start: index, + length: num, + file: file_number, + }; + drive.push(file); + files.insert(file_number, file); + + file_number += 1; + } else { + drive.push(Record::Gap { start: index, length: num }); + } + index += num; + is_file = !is_file; + } + + return Drive { in_order: drive, files: files }; +} + +fn print_enums(drive: &Drive) { + for x in drive.in_order.iter() { + match x { + Record::Gap { start: _, length } => { + for _ in 0..*length { + print!("."); + } + }, + Record::File { start: _, length, file } => { + for _ in 0..*length { + print!("{}", file); + } + } + } + } + print!("\n"); +} + +fn compress_enums(mut drive: Drive) -> Drive { + // for each file in backwards order, + for file_number in (0..(drive.files.len() as i32)).rev() { + let record = drive.files.get(&file_number); + println!("looking at {}: {:?}", file_number, record); + + // index into the in_order vec + let file_index = find_order_index(&drive, file_number); + + match record { + // we will never find a gap in the files hashmap + Some(Record::Gap {start: _, length: _}) => (), + // if we found none then it's probably an off-by-one at the edges + None => (), + + Some(Record::File {start: file_start, length: file_length, file: _ }) => { + // find the first gap where the object can live, + for gap_index in 0..drive.in_order.len() { + // if we're looking past the file, then we're done! + if gap_index > file_index { break; } + + match drive.in_order[gap_index] { + // if we're looking at a file, skip it. can't put our file into an occupied spot. + Record::File { .. } => { continue; }, + // if we're looking at a gap, proceed! + Record::Gap { start: gap_start, length: gap_length } => { + // found a gap that fits just right + if gap_length == *file_length { + + // remove the gap + drive.in_order.remove(gap_index); + // insert the file into the gap + let new_file = Record::File { + start: gap_start, + length: *file_length, + file: file_number + }; + drive.in_order.insert(gap_index, new_file); + + // remove the file + drive.in_order.remove(file_index); + // insert the gap where the file was + let new_gap = Record::Gap { + start: *file_start, + length: gap_length + }; + drive.in_order.insert(file_index, new_gap); + + // update the files hashmap + drive.files.insert(file_number, new_file); + + break; + } + + // the gap is bigger than the file + if gap_length > *file_length { + + let mut offset: i32 = 0; + // remove the gap + drive.in_order.remove(gap_index); + offset -= 1; + // insert the file into the gap + let new_file = Record::File { + start: gap_start, + length: *file_length, + file: file_number + }; + drive.in_order.insert(gap_index, new_file); + offset += 1; + + // fill the gap + if let Record::Gap { + start: _next_gap_start, + length: next_gap_length + } = drive.in_order[gap_index + 1] { + // if the next record is a gap, merge them + drive.in_order[gap_index + 1] = Record::Gap { + start:gap_start + *file_length, + length: gap_length - file_length + next_gap_length + }; + } else { + // if the next record is a file, add a gap. + let new_gap_fill = Record::Gap { + start: gap_start + *file_length, + length: gap_length - file_length + }; + drive.in_order.insert(gap_index + 1, new_gap_fill); + offset += 1; + } + + + + let offset_index = (file_index as i32 + offset) as usize; + // remove the file + drive.in_order.remove(offset_index); + // insert the gap where the file was + let new_gap = Record::Gap { + start: *file_start, + length: *file_length + }; + drive.in_order.insert(offset_index, new_gap); + // if the new gap has a gap after it, merge them + + if offset_index < drive.in_order.len()-1 { + if let Record::Gap { + start: _next_gap_start, + length: next_gap_length + } = drive.in_order[offset_index + 1] { + drive.in_order.remove(offset_index + 1); + drive.in_order[offset_index] = Record::Gap { + start: *file_start, + length: *file_length + next_gap_length + }; + } + } + // if the new gap has a gap before it, merge them + if offset_index > 0 { + if let Record::Gap { + start: prev_gap_start, + length: prev_gap_length + } = drive.in_order[offset_index - 1] { + drive.in_order.remove((file_index as i32 + offset) as usize); + drive.in_order[offset_index - 1] = Record::Gap { + start: prev_gap_start, + length: prev_gap_length + *file_length + }; + } + } + + // update the files hashmap + drive.files.insert(file_number, new_file); + + break; + } + + // if the gap is neither bigger than the file nor equal to the file it won't fit! + }, + } + + } + } + } + } + + return drive; +} + +fn checksum_enum(drive: &Drive) -> i64 { + let mut sum: i64 = 0; + for record in drive.in_order.iter() { + match record { + Record::Gap { start: _, length: _ } => (), + Record::File { start, length, file } => { + for i in 0..*length { + sum += (file * (i + start)) as i64; + } + } + } + } + + return sum; +} + +fn find_order_index(drive: &Drive, file_number: i32) -> usize { + for i in 0..drive.in_order.len() { + if let Record::File { file, .. } = drive.in_order[i] { + if file == file_number { + return i; + } + } + } + return usize::MAX; +} \ No newline at end of file diff --git a/day10/Cargo.toml b/day10/Cargo.toml new file mode 100644 index 0000000..3c916ef --- /dev/null +++ b/day10/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day10" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day10/src/main.rs b/day10/src/main.rs new file mode 100644 index 0000000..1827cd5 --- /dev/null +++ b/day10/src/main.rs @@ -0,0 +1,124 @@ +use std::collections::HashSet; +use std::collections::HashMap; +use std::fs; +use std::env; +use crate::Dir::{North, South, East, West }; + +// nodes are (coord, direction) pairs. +// neighbors are step1+turnleft, step2+turnleft, step3+turnleft, step1+turnright, etc +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +enum Dir { North, South, East, West } + +fn step(start: &Coord, d: Dir, steps: i32) -> Coord { + match d { + North => (start.0, start.1 - steps), + South => (start.0, start.1 + steps), + East => (start.0 + steps, start.1), + West => (start.0 - steps, start.1), + } +} + +type Coord = (i32, i32); + +fn main() { + println!("Hello, AoC day 04!"); + + let args: Vec = env::args().collect(); + if args.len() != 2 { + println!("wrong number of arguments!"); + std::process::exit(1); + } + + let file_path = &args[1]; + + let contents = fs::read_to_string(file_path).expect("Should have been able to read the file"); + let mut grid: HashMap = HashMap::new(); + let mut trailheads: HashSet = HashSet::new(); + + let mut x; + // build our grid! + let mut y = 0; + for line in contents.lines() { + x = 0; + for c in line.chars() { + let num = c.to_string().parse::().unwrap(); + let coords = (x,y); + if num == 0 { + trailheads.insert(coords); + } + grid.insert(coords, num); + x += 1; + } + y += 1; + } + + let mut sum: i32 = 0; + for head in &trailheads { + sum += score(*head, &grid); + } + println!("there are {sum} points in score total"); + + let mut sum: i32 = 0; + for head in &trailheads { + sum += rating(*head, &grid); + } + println!("there are {sum} points in rating total") +} + +fn score(head: Coord, grid: &HashMap) -> i32 { + let mut visited: HashSet = HashSet::new(); + let mut front: HashSet = HashSet::from([head]); + let mut peaks: HashSet = HashSet::new(); + + while !front.is_empty() { + let mut new_front: HashSet = HashSet::new(); + + for x in front.drain() { + if grid[&x] == 9 { + peaks.insert(x); + } + visited.insert(x); + for x2 in neighbors(&x, &grid) { + if visited.contains(&x2) { continue; } + new_front.insert(x2); + } + } + front = new_front; + } + return peaks.len() as i32; +} + +fn rating(head: Coord, grid: &HashMap) -> i32 { + let mut visited: HashSet = HashSet::new(); + let mut front: Vec = vec![head]; + + let mut points = 0; + while !front.is_empty() { + let mut new_front: Vec = vec![]; + + for x in front.drain(..) { + if grid[&x] == 9 { + points += 1; + } + visited.insert(x); + let x_neighbors = neighbors(&x, &grid); + + for x2 in x_neighbors { + if visited.contains(&x2) { continue; } + new_front.push(x2); + } + } + front = new_front; + } + return points as i32; +} + + +fn neighbors(head: &Coord, grid: &HashMap) -> HashSet { + let height = grid[head]; + let neighbors = vec![North, South, East, West] + .into_iter() + .map(|x| step(head, x, 1)) + .filter(|x| grid.contains_key(&x) && grid[&x]==height+1); + return neighbors.collect(); +} \ No newline at end of file diff --git a/day11/Cargo.toml b/day11/Cargo.toml new file mode 100644 index 0000000..3924912 --- /dev/null +++ b/day11/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "day11" +version = "0.1.0" +edition = "2021" + +[dependencies] +memoize = "0.4.2" diff --git a/day11/src/main.rs b/day11/src/main.rs new file mode 100644 index 0000000..94a3cd8 --- /dev/null +++ b/day11/src/main.rs @@ -0,0 +1,69 @@ +use std::iter; +use std::env; + +use std::fs; +use memoize::memoize; + +fn main() { + println!("Hello, AoC day 09!"); + + let args: Vec = env::args().collect(); + if args.len() != 2 { + println!("wrong number of arguments!"); + std::process::exit(1); + } + + let file_path = &args[1]; + + let initial_stones = fs::read_to_string(file_path) + .expect("should be able to read the file") + .trim() + .to_string(); + let mut stones = initial_stones + .split(" ") + .map(|s| s.to_string()) + .collect::>(); + + for _i in 0..25 { + stones = stones.into_iter().flat_map(blink).collect(); + } + println!("there were {} stones after 25 blinks", stones.len()); + + let limit = 75; + let mut count = 0; + for s in initial_stones.split(" ") { + count += blink_recursive(s.parse().unwrap(), limit); + } + println!("there were {count} stones after {limit} blinks!"); +} + +fn blink(number: String) -> Box>{ + if number == "0" { + return Box::new(iter::once("1".to_string())); + } + let l = number.len(); + if l % 2 == 0 { + return Box::new(iter::once(number[..l/2].parse::().unwrap().to_string()) + .chain(iter::once(number[l/2..].parse::().unwrap().to_string()))); + } + let number = number.parse::().unwrap(); + let new_string = (number*2024).to_string(); + return Box::new(iter::once(new_string)); +} + +#[memoize] +fn blink_recursive(label: i128, blinks: i32) -> i128 { + if blinks == 0 { + return 1; + } else if label == 0 { + return blink_recursive(1, blinks - 1); + } + let s = label.to_string(); + let len = s.len(); + if len % 2 == 0 { + let first: i128 = s[..len/2].parse().unwrap(); + let second: i128 = s[len/2..].parse().unwrap(); + return blink_recursive(first, blinks - 1) + blink_recursive(second, blinks - 1); + } + return blink_recursive(2024*label, blinks - 1); +} diff --git a/day12/Cargo.toml b/day12/Cargo.toml new file mode 100644 index 0000000..be558ad --- /dev/null +++ b/day12/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day12" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day12/src/main.rs b/day12/src/main.rs new file mode 100644 index 0000000..b111372 --- /dev/null +++ b/day12/src/main.rs @@ -0,0 +1,252 @@ +use std::collections::HashSet; +use std::collections::HashMap; +use std::fs; +use std::env; +use crate::Dir::{North, South, East, West }; + +// nodes are (coord, direction) pairs. +// neighbors are step1+turnleft, step2+turnleft, step3+turnleft, step1+turnright, etc +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +enum Dir { North, South, East, West } + +fn step(start: &Coord, d: Dir, steps: i32) -> Coord { + match d { + North => (start.0, start.1 - steps), + South => (start.0, start.1 + steps), + East => (start.0 + steps, start.1), + West => (start.0 - steps, start.1), + } +} + +type Coord = (i32, i32); + +fn main() { + println!("Hello, AoC day 04!"); + + let args: Vec = env::args().collect(); + if args.len() != 2 { + println!("wrong number of arguments!"); + std::process::exit(1); + } + + let file_path = &args[1]; + + let contents = fs::read_to_string(file_path).expect("Should have been able to read the file"); + let mut grid: HashMap = HashMap::new(); + + let mut x; + // build our grid! + let mut y = 0; + for line in contents.lines() { + x = 0; + for c in line.chars() { + let coords = (x,y); + grid.insert(coords, c); + x += 1; + } + y += 1; + } + + + // part a + let mut sum = 0; + + let mut cache: Vec>= Vec::new(); + for (location, _color) in &grid { + cache = fill_region_and_cache(location, &grid, cache); + } + + println!("we found {} regions", cache.len()); + for region in &cache { + println!("a region with color {} was found to have {} squares", + grid.get(region.iter().nth(0).unwrap()).unwrap(), + region.len()); + } + + for region in &cache { + let mut total_perimeter = 0; + let mut area = 0; + for location in region { + total_perimeter += perimeter(&location, &grid); + area += 1; + } + sum += area * total_perimeter; + } + + println!("the total cost is {sum}"); + + + // part b + // a list of sides (a side is a set of sidesegments) + let mut sides_cache: Vec> = Vec::new(); + + // for every location in the grid, + for (location, _color) in &grid { + // for every side ajdacent to that location, update the side cache with its side. + for sideseg in sides(location, &grid) { + sides_cache = spread_over_side_and_cache( + &sideseg, + &grid, + sides_cache); + } + } + + sum = 0; + + for region in &cache { + let mut sides = 0; + let mut area = 0; + for side in &sides_cache { + let first_location = side.iter().nth(0).unwrap().1; + if region.contains(&first_location) { + sides += 1; + } + } + + area += region.len() as i32; + + let color = grid.get(region.iter().nth(0).unwrap()).unwrap(); + + println!("a region with label {color} has area {area} and {sides} sides"); + + sum += area * sides; + } + + println!("the total cost is {sum}"); + + + +} + +fn neighbors(head: &Coord) -> Vec { + return vec![North, South, East, West] + .into_iter() + .map(|x| step(head, x, 1)) + .collect(); +} + +fn same_neighbors(location: &Coord, grid: &HashMap) -> Vec { + if let Some(me) = grid.get(location) { + return vec![North, South, East, West] + .into_iter() + .map(|x| step(location, x, 1)) + .filter(|x| grid.contains_key(x)) + .filter(|x| me == grid.get(x).expect("tried to look for a missing grid square")) + .collect(); + } + return vec![]; +} + +fn fill_region_and_cache( + location: &Coord, + grid: &HashMap, + mut cache: Vec> +) -> Vec> { + let mut new_region: HashSet = HashSet::from([*location]); + let mut accumulator: HashSet = HashSet::new(); + while !new_region.is_empty() { + let mut blah: HashSet = HashSet::new(); + for x in &new_region { + for existing_region in &cache { + if existing_region.contains(x) { + return cache; + } + } + } + + for x in &new_region { + for n in same_neighbors(&x, grid) { + if !accumulator.contains(&n) { + blah.insert(n); + } + } + } + + for x in new_region.drain() { + accumulator.insert(x); + } + + new_region = blah; + } + + cache.push(accumulator); + + return cache; +} + +fn spread_over_side_and_cache( + side: &SideSegment, + grid: &HashMap, + mut cache: Vec> +) -> Vec> { + let mut wavefront: HashSet = HashSet::from([*side]); + let mut accumulator: HashSet = HashSet::new(); + while !wavefront.is_empty() { + // we're iterating over the wavefront of squares to look at for whether they're + // part of the side we're building. + + let mut new_wavefront: HashSet = HashSet::new(); + + // look at each side in the wavefront + for x in &wavefront { + // if it's in a side that we already have cached, then we're done. + for existing_side in &cache { + if existing_side.contains(x) { + // return the cache unchanged. + return cache; + } + } + } + + // look at each side in the wavefrnot, to build up a new wavefront. + for SideSegment(dir, coord) in &wavefront { + + for n in same_neighbors(&coord, grid) + // look at neighbors in this region. + .into_iter() + .flat_map(|next_coord| sides(&next_coord, grid)) + // look at sides of those neighbors. + .filter(|SideSegment(dir2, _coord2)| dir2 == dir) { + // filter down to just sides in the same direction. + // n is a neighboring side with the same direction as us. + if !accumulator.contains(&n) { + // if n isn't already in the accumulator, add n to the new wavefront. + new_wavefront.insert(n); + } + } + } + + // add all the siddes in the wavefront to the accumulator. + for x in wavefront.drain() { + accumulator.insert(x); + } + + wavefront = new_wavefront; + } + + cache.push(accumulator); + + return cache; +} + +fn perimeter(location: &Coord, grid: &HashMap) -> i32 { + let mut perimeter = 0; + for neighbor in neighbors(location) { + let neighboring_region = grid.get(&neighbor); + if neighboring_region != grid.get(location) { + perimeter += 1; + } + } + return perimeter; +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +struct SideSegment (Dir, Coord); + +fn sides(location: &Coord, grid: &HashMap) -> Vec { + return vec![North, South, East, West] + .into_iter() + .map(|x| SideSegment(x, *location) ) + .filter(|SideSegment(dir, loc)| grid.get(loc) != grid.get(&step(loc, *dir, 1)) ) + .collect(); +} \ No newline at end of file