diff options
-rw-r--r-- | 2020/rust/Cargo.toml | 4 | ||||
-rw-r--r-- | 2020/rust/day11/Cargo.toml | 10 | ||||
-rw-r--r-- | 2020/rust/day11/src/main.rs | 345 |
3 files changed, 357 insertions, 2 deletions
diff --git a/2020/rust/Cargo.toml b/2020/rust/Cargo.toml index a51672a..19751a7 100644 --- a/2020/rust/Cargo.toml +++ b/2020/rust/Cargo.toml @@ -10,8 +10,8 @@ members = [ "day07", "day08", "day09", -# "day10", -# "day11", + "day10", + "day11", # "day12", # "day13", # "day14", diff --git a/2020/rust/day11/Cargo.toml b/2020/rust/day11/Cargo.toml new file mode 100644 index 0000000..00a1103 --- /dev/null +++ b/2020/rust/day11/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day11" +version = "0.1.0" +authors = ["cos <cos>"] +edition = "2018" + +[dependencies] +aoc = { path = "../aoc" } +anyhow = "1.0" +thiserror = "1.0" diff --git a/2020/rust/day11/src/main.rs b/2020/rust/day11/src/main.rs new file mode 100644 index 0000000..014caed --- /dev/null +++ b/2020/rust/day11/src/main.rs @@ -0,0 +1,345 @@ +use anyhow::{Result, anyhow}; +use std::env::args; +use std::fmt; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::ops::AddAssign; +use std::path::Path; +use thiserror::Error; + +#[derive(Clone, Copy)] +struct CoordPair { + x: isize, + y: isize, +} + +impl AddAssign for CoordPair { + fn add_assign(&mut self, other: Self) { + *self = Self { + x: self.x + other.x, + y: self.y + other.y, + }; + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum SeatState { + Floor, + Empty, + Occupied, +} + +#[derive(Error, Debug)] +pub enum SeatStateError { + #[error("Could not access SeatState")] + Invalid, +} + +#[derive(PartialEq)] +struct WaitingArea { + width: isize, + height: isize, + grid: Vec<SeatState>, +} + +#[derive(Error, Debug)] +pub enum WaitingAreaError { + #[error("Could not access WaitingArea")] + Invalid, +} + +impl WaitingArea { + fn get_seat_state(&self, pos: CoordPair) -> Option<SeatState> { + self.grid.get((pos.y * self.width + pos.x) as usize).copied() + } + + fn set_seat_state(&mut self, pos: CoordPair, state: SeatState) { + let entry = self.grid.get_mut((pos.y * self.width + pos.x) as usize).unwrap(); + *entry = state; + } + + fn count_neighbours(&self, pos: CoordPair) -> isize { + let mut neighbours = 0; + + let x_start = match (pos.x as isize)-1 { + n if n < 0 => 0, + n => n as isize, + }; + let y_start = match (pos.y as isize)-1 { + n if n < 0 => 0, + n => n as isize, + }; + let x_end = match pos.x+1 { + n if n > self.width-1 => self.width-1, + n => n, + }; + let y_end = match pos.y+1 { + n if n > self.height-1 => self.height-1, + n => n, + }; + + for y in y_start..=y_end { + for x in x_start..=x_end { + if x == pos.x && y == pos.y { + continue; + } + match self.get_seat_state(CoordPair {x, y}) { + Some(SeatState::Occupied) => neighbours += 1, + _ => { }, + } + } + } + + neighbours + } + + fn count_visible(&self, pos: CoordPair) -> isize { + let slopes = vec![ + CoordPair { x: 1, y: 0, }, + CoordPair { x: 1, y: 1, }, + CoordPair { x: 0, y: 1, }, + CoordPair { x: -1, y: 1, }, + CoordPair { x: -1, y: 0, }, + CoordPair { x: -1, y: -1, }, + CoordPair { x: 0, y: -1, }, + CoordPair { x: 1, y: -1, }, + ]; + + let visible_occupied = slopes.iter().map(|slope| { + let mut check_pos = CoordPair { x: pos.x, y: pos.y, }; + check_pos += *slope; + while check_pos.x < self.width && check_pos.y < self.height && + check_pos.x >= 0 && check_pos.y >= 0 + { + match self.get_seat_state(check_pos) { + Some(SeatState::Occupied) => return 1, + Some(SeatState::Floor) => {}, + _ => return 0, + } + check_pos += *slope; + } + + 0 + }).sum(); + + visible_occupied + } + + fn clone(&self) -> WaitingArea { + let mut grid = vec![]; + + for y in 0..self.height { + for x in 0..self.width { + match self.get_seat_state(CoordPair {x, y}) { + Some(SeatState::Floor) => grid.push(SeatState::Floor), + Some(SeatState::Empty) => grid.push(SeatState::Empty), + Some(SeatState::Occupied) => grid.push(SeatState::Occupied), + None => panic!("You've been eaten by a grue"), + }; + } + } + + WaitingArea { + height: self.height, + width: self.width, + grid, + } + } + + fn apply_rules(&self) -> WaitingArea { + let mut grid = vec![]; + + for y in 0..self.height { + for x in 0..self.width { + match self.get_seat_state(CoordPair {x, y}) { + Some(SeatState::Floor) => grid.push(SeatState::Floor), + Some(SeatState::Empty) => { + if self.count_neighbours(CoordPair {x, y}) > 0 { + grid.push(SeatState::Empty); + } else { + grid.push(SeatState::Occupied); + } + }, + Some(SeatState::Occupied) => if self.count_neighbours(CoordPair {x, y}) >= 4 { + grid.push(SeatState::Empty); + } else { + grid.push(SeatState::Occupied); + }, + None => panic!("You've been eaten by a grue"), + }; + } + } + + WaitingArea { + height: self.height, + width: self.width, + grid, + } + } + + fn apply_new_rules(&self) -> WaitingArea { + let mut grid = vec![]; + + for y in 0..self.height { + for x in 0..self.width { + match self.get_seat_state(CoordPair {x, y}) { + Some(SeatState::Floor) => grid.push(SeatState::Floor), + Some(SeatState::Empty) => { + if self.count_visible(CoordPair {x, y}) > 0 { + grid.push(SeatState::Empty); + } else { + grid.push(SeatState::Occupied); + } + }, + Some(SeatState::Occupied) => if self.count_visible(CoordPair {x, y}) >= 5 { + grid.push(SeatState::Empty); + } else { + grid.push(SeatState::Occupied); + }, + None => panic!("You've been eaten by a grue"), + }; + } + } + + WaitingArea { + height: self.height, + width: self.width, + grid, + } + } + + fn count_seats(&self) -> (isize, isize, isize ) { + let ( mut empty, mut occupied, mut floor ) = ( 0, 0, 0 ); + + for y in 0..self.height { + for x in 0..self.width { + match self.get_seat_state(CoordPair {x, y}) { + Some(SeatState::Floor) => floor += 1, + Some(SeatState::Empty) => empty += 1, + Some(SeatState::Occupied) => occupied += 1, + None => panic!("You've been eaten by a grue"), + }; + } + } + + ( empty, occupied, floor ) + } +} + +impl fmt::Debug for WaitingArea { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "WaitingArea: [")?; + for y in 0..self.height { + for x in 0..self.width { + match self.get_seat_state(CoordPair {x, y}) { + Some(SeatState::Floor) => write!(f, "."), + Some(SeatState::Empty) => write!(f, "L"), + Some(SeatState::Occupied) => write!(f, "#"), + None => write!(f, "You've been eaten by a grue"), + }?; + } + writeln!(f)?; + } + + f.write_str("] ") + } +} + + +fn read_input<T: AsRef<Path>>(filename: T) -> Result<WaitingArea> { + let f = File::open(filename)?; + let reader = BufReader::new(f); + + let waiting_area = reader.lines() + .fold(Ok(WaitingArea { width: 0, height: 0, grid: vec![] }), |acc, l| { + let line = l?; + let row: Result<Vec<SeatState>> = line.chars().map(|c| match c { + 'L' => Ok(SeatState::Empty), + '.' => Ok(SeatState::Floor), + '#' => Ok(SeatState::Occupied), + _ => Err(anyhow!("Invalid map character: '{}'", c)), + }).collect(); + + match acc { + Ok(mut a) => { + a.grid.append(&mut row?); + a.height += 1; + Ok(a) + }, + Err(err) => Err(err) + } + }); + match waiting_area { + Ok(mut wa) => { + wa.width = wa.grid.len() as isize/wa.height; + Ok(wa) + }, + err => err, + } +} + +fn part1(input: &WaitingArea) -> Result<isize> { + let mut waiting_area = input.clone(); + loop { + let next = waiting_area.apply_rules(); + + if next == waiting_area { + break; + } + waiting_area = next; + } + + let ( _empty, occupied, _floor ) = waiting_area.count_seats(); + Ok(occupied) +} + +fn part2(input: &WaitingArea) -> Result<isize> { + let mut waiting_area = input.clone(); + loop { + let next = waiting_area.apply_new_rules(); + + if next == waiting_area { + break; + } + waiting_area = next; + } + + let ( _empty, occupied, _floor ) = waiting_area.count_seats(); + Ok(occupied) +} + +fn main() { + let ( do_part_1, do_part_2 ) = aoc::do_parts(); + + let filename = match args().nth(1) { + Some(f) => f, + None => { + eprintln!("Missing input filename"); + std::process::exit(1); + }, + }; + match read_input(filename) { + Ok(input) => { + if do_part_1 { + match part1(&input) { + Ok(solution) => println!("Part1: {}", solution), + Err(err) => { + eprintln!("Part1, no solution found: {}", err); + std::process::exit(1); + } + }; + } + if do_part_2 { + match part2(&input) { + Ok(solution) => println!("Part2: {}", solution), + Err(err) => { + eprintln!("Part2, no solution found: {}", err); + std::process::exit(1); + } + }; + } + }, + Err(err) => eprintln!("Could not read input: {}", err), + } +} + |