days 9 10 11 and 12
This commit is contained in:
		
							parent
							
								
									bd93ac4c3d
								
							
						
					
					
						commit
						14c1e412eb
					
				
							
								
								
									
										6
									
								
								day09/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								day09/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| [package] | ||||
| name = "day09" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
							
								
								
									
										345
									
								
								day09/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								day09/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<Record>, | ||||
|     files: HashMap<i32, Record>, | ||||
| } | ||||
| fn main() { | ||||
|     println!("Hello, AoC day 09!"); | ||||
| 
 | ||||
|     let args: Vec<String> = 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<i32> { | ||||
|     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::<i32>(); | ||||
|         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<i32> = 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::<u32>(); | ||||
|         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<i32>) { | ||||
|     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<Record> = Vec::with_capacity(contents.len()); | ||||
|     let mut files: HashMap<i32, Record> = 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::<i32>().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; | ||||
| } | ||||
							
								
								
									
										6
									
								
								day10/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								day10/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| [package] | ||||
| name = "day10" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
							
								
								
									
										124
									
								
								day10/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								day10/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<String> = 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<Coord, i8> = HashMap::new(); | ||||
|     let mut trailheads: HashSet<Coord> = 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::<i8>().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<Coord, i8>) -> i32 { | ||||
|     let mut visited: HashSet<Coord> = HashSet::new(); | ||||
|     let mut front: HashSet<Coord> = HashSet::from([head]); | ||||
|     let mut peaks: HashSet<Coord> = HashSet::new(); | ||||
| 
 | ||||
|     while !front.is_empty() { | ||||
|         let mut new_front: HashSet<Coord> = 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<Coord, i8>) -> i32 { | ||||
|     let mut visited: HashSet<Coord> = HashSet::new(); | ||||
|     let mut front: Vec<Coord> = vec![head]; | ||||
| 
 | ||||
|     let mut points = 0; | ||||
|     while !front.is_empty() { | ||||
|         let mut new_front: Vec<Coord> = 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<Coord, i8>) -> HashSet<Coord> { | ||||
|     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(); | ||||
| } | ||||
							
								
								
									
										7
									
								
								day11/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								day11/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| [package] | ||||
| name = "day11" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
| memoize = "0.4.2" | ||||
							
								
								
									
										69
									
								
								day11/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								day11/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<String> = 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::<Vec<String>>(); | ||||
| 
 | ||||
|     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<dyn Iterator<Item=String>>{ | ||||
|     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::<u128>().unwrap().to_string()) | ||||
|         .chain(iter::once(number[l/2..].parse::<u128>().unwrap().to_string()))); | ||||
|     } | ||||
|     let number = number.parse::<u128>().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); | ||||
| } | ||||
							
								
								
									
										6
									
								
								day12/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								day12/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| [package] | ||||
| name = "day12" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
							
								
								
									
										252
									
								
								day12/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								day12/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<String> = 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<Coord, char> = 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<HashSet<Coord>>= 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<HashSet<SideSegment>> = 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<Coord> { | ||||
|     return vec![North, South, East, West] | ||||
|         .into_iter() | ||||
|         .map(|x| step(head, x, 1)) | ||||
|         .collect(); | ||||
| } | ||||
| 
 | ||||
| fn same_neighbors(location: &Coord, grid: &HashMap<Coord, char>) -> Vec<Coord> { | ||||
|     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<Coord, char>, 
 | ||||
|     mut cache: Vec<HashSet<Coord>> | ||||
| ) -> Vec<HashSet<Coord>> { | ||||
|     let mut new_region: HashSet<Coord> = HashSet::from([*location]); | ||||
|     let mut accumulator: HashSet<Coord> = HashSet::new(); | ||||
|     while !new_region.is_empty() { | ||||
|         let mut blah: HashSet<Coord> = 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<Coord, char>, 
 | ||||
|     mut cache: Vec<HashSet<SideSegment>> | ||||
| ) -> Vec<HashSet<SideSegment>> { | ||||
|     let mut wavefront: HashSet<SideSegment> = HashSet::from([*side]); | ||||
|     let mut accumulator: HashSet<SideSegment> = 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<SideSegment> = 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<Coord, char>) -> 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<Coord, char>) -> Vec<SideSegment> { | ||||
|     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(); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user