From 07e60724657c332ca114da378aa3e1d1e504133e Mon Sep 17 00:00:00 2001 From: inga-lovinde <52715130+inga-lovinde@users.noreply.github.com> Date: Sat, 12 Dec 2020 00:25:24 +0100 Subject: [PATCH] Refactored day 11 --- day11/Cargo.toml | 3 + day11/src/binary.rs | 104 ++++++++++++++++++++ day11/src/board_metadata.rs | 85 ++++++++++++++++ day11/src/game.rs | 161 +++++++++++++++++++++++++++++++ day11/src/main.rs | 186 +++--------------------------------- day11/src/rules.rs | 9 ++ day11/src/rules_easy.rs | 28 ++++++ 7 files changed, 403 insertions(+), 173 deletions(-) create mode 100644 day11/src/binary.rs create mode 100644 day11/src/board_metadata.rs create mode 100644 day11/src/game.rs create mode 100644 day11/src/rules.rs create mode 100644 day11/src/rules_easy.rs diff --git a/day11/Cargo.toml b/day11/Cargo.toml index 2f24ab3..5f54999 100644 --- a/day11/Cargo.toml +++ b/day11/Cargo.toml @@ -7,4 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +enum-map = "0.6.4" ndarray = "0.14.0" +strum = "0.20.0" +strum_macros = "0.20.1" diff --git a/day11/src/binary.rs b/day11/src/binary.rs new file mode 100644 index 0000000..b015095 --- /dev/null +++ b/day11/src/binary.rs @@ -0,0 +1,104 @@ +use enum_map::Enum; +use strum_macros::EnumIter; + +#[derive(Clone, Copy, Enum, Eq, PartialEq)] +pub enum State { + None, + Floor, + SeatEmpty, + SeatOccupied, +} + +impl Default for State { + fn default() -> Self { Self::None } +} + +impl State { + fn from_number(number: u8) -> Self { + match number { + 0 => Self::None, + 1 => Self::Floor, + 2 => Self::SeatEmpty, + 3 => Self::SeatOccupied, + _ => panic!("unsupported number {}", number), + } + } + + fn get_number(&self) -> u8 { + match self { + Self::None => 0, // border should always be 0 + Self::Floor => 1, + Self::SeatEmpty => 2, + Self::SeatOccupied => 3, + } + } +} + +#[derive(Copy, Clone, Enum, EnumIter)] +pub enum Direction { + UpLeft, + Up, + UpRight, + Left, + Right, + DownLeft, + Down, + DownRight, +} + +impl Direction { + fn get_offset(&self) -> u16 { + match self { + Self::UpLeft => 0, + Self::Up => 2, + Self::UpRight => 4, + Self::Left => 6, + Self::Right => 8, + Self::DownLeft => 10, + Self::Down => 12, + Self::DownRight => 14, + } + } +} + +pub struct CellState { + neighbours_states: u16, + state: u8, +} + +impl CellState { + pub fn new() -> Self { + Self { + neighbours_states: 0, + state: 0, + } + } + + pub fn update_neighbour_state(&mut self, direction: Direction, new_state: State) -> () { + self.neighbours_states = (self.neighbours_states & !(0b11 << direction.get_offset())) | ((new_state.get_number() as u16) << direction.get_offset()); + } + + pub fn update_state(&mut self, new_state: State) { + self.state = new_state.get_number(); + } + + pub fn from_number(number: u32) -> Self { + CellState { + state: (number >> 16) as u8, + neighbours_states: (number & 0xffff) as u16, + } + } + + pub fn get_number(&self) -> u32 { + ((self.state as u32) << 16) | (self.neighbours_states as u32) + } + + pub fn get_state(&self) -> State { + State::from_number(self.state) + } + + pub fn get_neighbour_state(&self, direction: Direction) -> State { + State::from_number(((self.neighbours_states >> direction.get_offset()) & 0b11) as u8) + } +} + diff --git a/day11/src/board_metadata.rs b/day11/src/board_metadata.rs new file mode 100644 index 0000000..f5b1c24 --- /dev/null +++ b/day11/src/board_metadata.rs @@ -0,0 +1,85 @@ +use std::default::Default; +use std::ops::{Index, IndexMut}; +use ndarray::Array2; + +use crate::binary::Direction; + +#[derive(Copy, Clone)] +pub struct CellLocation { + row: usize, + column: usize, +} + +impl CellLocation { + pub fn new(row: usize, column: usize) -> Self { + Self { + row, + column, + } + } + + fn new_option(row_option: Option, column_option: Option) -> Option { + match (row_option, column_option) { + (Some(row), Some(column)) => Some(Self::new(row, column)), + _ => None, + } + } +} + +impl Index for Array2 { + type Output = T; + fn index<'a>(&'a self, cell_location: CellLocation) -> &'a T { + &self[[cell_location.row, cell_location.column]] + } +} + +impl IndexMut for Array2 { + fn index_mut<'a>(&'a mut self, cell_location: CellLocation) -> &'a mut T { + &mut self[[cell_location.row, cell_location.column]] + } +} + +pub struct BoardMetadata { + rows: usize, + columns: usize, +} + +impl BoardMetadata { + pub fn new(rows: usize, columns: usize) -> Self { + BoardMetadata { + rows, + columns, + } + } + + pub fn get_neighbour_location(&self, cell_location: CellLocation, direction: Direction) -> Option { + let row = cell_location.row; + let column = cell_location.column; + + let up = if row > 0 { Some(row-1) } else { None }; + let middle = Some(row); + let down = if row < self.rows-1 { Some(row+1) } else { None }; + let left = if column > 0 { Some(column-1) } else { None }; + let center = Some(column); + let right = if column < self.columns-1 { Some(column+1) } else { None }; + + match direction { + Direction::UpLeft => CellLocation::new_option(up, left), + Direction::Up => CellLocation::new_option(up, center), + Direction::UpRight => CellLocation::new_option(up, right), + Direction::Left => CellLocation::new_option(middle, left), + Direction::Right => CellLocation::new_option(middle, right), + Direction::DownLeft => CellLocation::new_option(down, left), + Direction::Down => CellLocation::new_option(down, center), + Direction::DownRight => CellLocation::new_option(down, right), + } + } + + pub fn create_board_default(&self) -> Array2 { + Array2::default((self.rows, self.columns)) + } + + pub fn create_board_from_shape_fn T>(&self, f: F) -> Array2 { + Array2::from_shape_fn((self.rows, self.columns), |(row, column)| f(CellLocation::new(row, column))) + } +} diff --git a/day11/src/game.rs b/day11/src/game.rs new file mode 100644 index 0000000..def8bc7 --- /dev/null +++ b/day11/src/game.rs @@ -0,0 +1,161 @@ +use enum_map::EnumMap; +use ndarray::Array2; +use strum::IntoEnumIterator; + +use crate::binary::CellState; +use crate::binary::Direction; +use crate::binary::State; +use crate::board_metadata::BoardMetadata; +use crate::board_metadata::CellLocation; +use crate::rules::Rules; + +struct CellInfo { + neighbours: EnumMap>, + state: CellState, +} + +impl CellInfo { + fn new(neighbours: EnumMap>) -> CellInfo { + CellInfo { + neighbours, + state: CellState::new(), + } + } + + fn update_neighbour_state(&mut self, direction: Direction, new_state: State) -> () { + self.state.update_neighbour_state(direction, new_state) + } + + fn update_state(&mut self, new_state: State) { + self.state.update_state(new_state) + } +} + +pub struct Game { + cell_rules: Vec, + board: Array2, + rows: usize, + columns: usize, +} + +impl Game { + // only updates the state of this cell for it and its neighbours; + // only state of this cell is used from new_cell + fn update_cell(&mut self, location: CellLocation, new_state: State) { + //println!("Updating cell {}:{}", location.row, location.column); + self.board[location].update_state(new_state); + + for direction in Direction::iter() { + match self.board[location].neighbours[direction] { + Some(neighbour_location) => { + //println!("Updating neighbour cell {}:{}", neighbour_location.row, neighbour_location.column); + self.board[neighbour_location].update_neighbour_state(direction, new_state); + }, + _ => {}, + } + } + } + + fn build_cell_rules() -> Vec { + let mut result = vec![State::None; 1 << 18]; + + for i in 0..1usize << 18 { + let original_state = CellState::from_number(i as u32); + let mut neighbour_counts = EnumMap::new(); + for direction in Direction::iter() { + neighbour_counts[original_state.get_neighbour_state(direction)] += 1usize; + } + + let new_state = T::get_next_state(original_state.get_state(), neighbour_counts); + result[i] = new_state; + //println!("Rule #{}: for state_counts [{}, {}, {}, {}] and old state {} new state is {}", i, state_counts[0], state_counts[1], state_counts[2], state_counts[3], current_state, new_state); + } + + result + } + + fn get_next_state(&self, cell_info: &CellInfo) -> State { + self.cell_rules[cell_info.state.get_number() as usize] + } + + pub fn next_step(&mut self) -> usize { + let mut changes: Vec<_> = vec![]; + for row in 0..self.rows { + for column in 0..self.columns { + let location = CellLocation::new(row, column); + let cell = &self.board[location]; + let next_state = self.get_next_state(cell); + //println!("location: {}:{}, neighbours state {}, old state {}, next state {}", location.row, location.column, cell.neighbours_states, cell.state, next_state); + if next_state != cell.state.get_state() { + changes.push((location, next_state)); + } + } + } + + let changes_count = changes.len(); + for (location, new_state) in changes { + self.update_cell(location, new_state); + } + + changes_count + } + + pub fn from_input(input_data: &[String]) -> Self { + let rows = input_data.len(); + let columns = input_data[0].len(); + let board_metadata = BoardMetadata::new(rows, columns); + let mut states = board_metadata.create_board_default(); + + for row in 0..rows { + let chars = input_data[row].chars().collect::>(); + for column in 0..columns { + let ch = chars[column]; + states[[row, column]] = match ch { + '.' => State::Floor, + 'L' => State::SeatEmpty, + '#' => State::SeatOccupied, + _ => State::None, + } + } + } + + let board = board_metadata.create_board_from_shape_fn(|cell_location| { + CellInfo::new(R::get_neighbours(cell_location, &board_metadata, &states)) + }); + + let cell_rules = Self::build_cell_rules::(); + + let mut game = Game { + rows, + columns, + board, + cell_rules, + }; + + for row in 0..rows { + for column in 0..columns { + let location = CellLocation::new(row, column); + game.update_cell(location, states[location]); + } + } + + return game; + } + + pub fn print_board(&self) { + for row in (&self.board).genrows() { + println!("{}", row.iter().map(|cell| { + match cell.state.get_state() { + State::Floor => '.', + State::SeatEmpty => 'L', + State::SeatOccupied => '#', + State::None => '0', + } + }).collect::()); + } + } + + pub fn get_count_of_cells_for_state(&self, state: State) -> usize { + (&self.board).iter().filter(|&cell| cell.state.get_state() == state).count() + } +} diff --git a/day11/src/main.rs b/day11/src/main.rs index f6d04fa..44fa209 100644 --- a/day11/src/main.rs +++ b/day11/src/main.rs @@ -1,193 +1,33 @@ #![feature(trait_alias)] use std::io::{self, BufRead}; -use ndarray::Array2; -// state (u32 for simplicity reasons): 0/1/2/3 (in this case, 1 = floor, 2 = seat free, 3 = seat taken) +mod binary; +mod board_metadata; +mod game; +mod rules; +mod rules_easy; -// every cell is u32, where: -// * lowest 16 bits describe its neighbours: -// * bits 0-1 (cell & 3) is the state of upper left neighbour, -// * bits 2-3 ((cell >> 2) & 3) is the state of upper neighbour, -// * bits 4-5 is the state of upper right neighbour, -// * bits 6-7, left, -// * bits 8-9, right, -// * bits 10-11, bottom left, -// * bits 12-13, bottom, -// * bits 14-15, bottom right -// * bits 16-17 ((cell >> 16) & 3) describe the cell itself - -// rule: cell (u32, 18 bits used) -> new cell (u32) - -// board is indexed by [row, column], -// where row is from top to bottom and column is from left to right - -trait StateRules = Fn([usize; 4], u32) -> u32; - -struct Game { - cell_rules: Vec, - board: Array2, - rows: usize, - columns: usize, -} - -impl Game { - // only updates the state of this cell for it and its neighbours; - // only state of this cell is used from new_cell - fn update_cell(&mut self, row: usize, column: usize, new_cell: u32) { - let state_diff = (new_cell ^ self.board[[row, column]]) >> 16; - - self.board[[row, column]] ^= state_diff << 16; - - self.board[[row+1, column+1]] ^= state_diff; - self.board[[row+1, column ]] ^= state_diff << 2; - self.board[[row+1, column-1]] ^= state_diff << 4; - self.board[[row , column+1]] ^= state_diff << 6; - self.board[[row , column-1]] ^= state_diff << 8; - self.board[[row-1, column+1]] ^= state_diff << 10; - self.board[[row-1, column ]] ^= state_diff << 12; - self.board[[row-1, column-1]] ^= state_diff << 14; - } - - fn build_cell_rules(state_rules: T) -> Vec { - let mut result = vec![0u32; 1 << 18]; - - for i in 0..1usize << 18 { - let cell = i as u32; - let current_state = (cell >> 16) & 3; - let mut state_counts = [0usize; 4]; - for j in 0..8 { - state_counts[((cell >> (2*j)) & 3) as usize] += 1; - } - - let new_state = state_rules(state_counts, current_state); - let new_cell = cell ^ ((current_state ^ new_state) << 16); - result[i] = new_cell; - } - - result - } - - pub fn next_step(&mut self) -> usize { - let mut changes: Vec<_> = vec![]; - for row in 1..self.rows-1 { - for column in 1..self.columns-1 { - let old_cell = self.board[[row, column]]; - let new_cell = self.cell_rules[old_cell as usize]; - if new_cell != old_cell { - changes.push((row, column, new_cell)); - } - } - } - - let changes_count = changes.len(); - for (row, column, new_cell) in changes { - self.update_cell(row, column, new_cell); - } - - changes_count - } - - pub fn from_input(input_data: &[String], state_rules: T) -> Game { - let rows = input_data.len() + 2; - let columns = input_data[0].len() + 2; - let mut states = Array2::zeros((rows, columns)); - - for row in 1..rows-1 { - let chars = input_data[row-1].chars().collect::>(); - for column in 1..columns-1 { - let ch = chars[column-1]; - states[[row, column]] = match ch { - '.' => 1, - 'L' => 2, - '#' => 3, - _ => 0, - } - } - } - - let mut board = Array2::zeros((rows, columns)); - - /* - board[[0, 0]] = states[[1, 1]] << 14; - board[[0, columns-1]] = states[[1, columns-2]] << 10; - board[[rows-1, 0]] = states[[rows-2, 1]] << 4; - board[[rows-1, columns-1]] = states[[rows-2, columns-2]]; - - for row in 1..rows-1 { - board[[row, 0]] = (states[[row-1, 1]] << 4) ^ (states[[row, 1]] << 8) ^ (states[[row+1, 1]] << 14); - board[[row, columns-1]] = (states[[row-1, columns-2]]) ^ (states[[row, columns-2]] << 6) ^ (states[[row+1, columns-2]] << 10); - } - - for column in 1..columns-1 { - board[[0, column]] = (states[[1, column-1]] << 10) ^ (states[[1, column]] << 12) ^ (states[[1, column+1]] << 14); - board[[rows-1, column]] = (states[[rows-2, column-1]]) ^ (states[[rows-2, column]] << 2) ^ (states[[rows-2, column+1]] << 4); - } - */ - - for row in 1..rows-1 { - for column in 1..columns-1 { - board[[row, column]] = - (states[[row-1, column-1]] ) ^ - (states[[row-1, column ]] << 2) ^ - (states[[row-1, column+1]] << 4) ^ - (states[[row , column-1]] << 6) ^ - (states[[row , column+1]] << 8) ^ - (states[[row+1, column-1]] << 10) ^ - (states[[row+1, column ]] << 12) ^ - (states[[row+1, column+1]] << 14) ^ - (states[[row, column]] << 16); - } - } - - let cell_rules = Self::build_cell_rules(state_rules); - - Game { - rows, - columns, - board, - cell_rules, - } - } - - pub fn print_board(&self) { - for row in (&self.board).genrows() { - println!("{}", row.iter().map(|state| { - match (state >> 16) & 3 { - 1 => '.', - 2 => 'L', - 3 => '#', - _ => '0', - } - }).collect::()); - } - } - - pub fn get_count_of_cells_for_state(&self, state: u32) -> usize { - (&self.board).iter().filter(|&&cell| ((cell >> 16) & 3) == state).count() - } -} +use binary::State; +use game::Game; +use rules_easy::RulesEasy; fn main() { let stdin = io::stdin(); let lines: Vec<_> = stdin.lock().lines().map(|line| line.unwrap()).collect(); - let mut game = Game::from_input(&lines, |state_counts: [usize; 4], current_state| { - match current_state { - 2 => if state_counts[3] == 0 { 3 } else { 2 }, - 3 => if state_counts[3] >= 4 { 2 } else { 3 }, - other => other - } - }); + let mut game = Game::from_input::(&lines); + + //game.print_board(); for i in 1.. { let changes_count = game.next_step(); println!("Iteration {}; changed cells: {}", i, changes_count); + //game.print_board(); if changes_count == 0 { break; } } - println!("Board stabilized at {} occupied seats", game.get_count_of_cells_for_state(3)); game.print_board(); - println!("Board stabilized at {} occupied seats", game.get_count_of_cells_for_state(3)); + println!("Board stabilized at {} occupied seats", game.get_count_of_cells_for_state(State::SeatOccupied)); } diff --git a/day11/src/rules.rs b/day11/src/rules.rs new file mode 100644 index 0000000..eb9dcec --- /dev/null +++ b/day11/src/rules.rs @@ -0,0 +1,9 @@ +use enum_map::EnumMap; +use ndarray::Array2; +use crate::binary::{Direction, State}; +use crate::board_metadata::{BoardMetadata,CellLocation}; + +pub trait Rules { + fn get_next_state(current_state: State, neighbour_counts: EnumMap) -> State; + fn get_neighbours(cell_location: CellLocation, board_metadata: &BoardMetadata, original_states: &Array2) -> EnumMap>; +} diff --git a/day11/src/rules_easy.rs b/day11/src/rules_easy.rs new file mode 100644 index 0000000..756b905 --- /dev/null +++ b/day11/src/rules_easy.rs @@ -0,0 +1,28 @@ +use enum_map::EnumMap; +use ndarray::Array2; +use strum::IntoEnumIterator; +use crate::binary::{Direction, State}; +use crate::board_metadata::{BoardMetadata,CellLocation}; +use crate::rules::Rules; + +pub struct RulesEasy {} + +impl Rules for RulesEasy { + fn get_next_state(current_state: State, neighbour_counts: EnumMap) -> State { + match current_state { + State::SeatEmpty => if neighbour_counts[State::SeatOccupied] == 0 { State::SeatOccupied } else { State::SeatEmpty }, + State::SeatOccupied => if neighbour_counts[State::SeatOccupied] >= 4 { State::SeatEmpty } else { State::SeatOccupied }, + other => other + } + } + + fn get_neighbours(cell_location: CellLocation, board_metadata: &BoardMetadata, _original_states: &Array2) -> EnumMap> { + let mut neighbours = EnumMap::new(); + + for direction in Direction::iter() { + neighbours[direction] = board_metadata.get_neighbour_location(cell_location, direction); + } + + neighbours + } +}