Refactored day 11

main
Inga 🏳‍🌈 3 years ago
parent f777a09f63
commit 07e6072465
  1. 3
      day11/Cargo.toml
  2. 104
      day11/src/binary.rs
  3. 85
      day11/src/board_metadata.rs
  4. 161
      day11/src/game.rs
  5. 186
      day11/src/main.rs
  6. 9
      day11/src/rules.rs
  7. 28
      day11/src/rules_easy.rs

@ -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"

@ -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)
}
}

@ -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<usize>, column_option: Option<usize>) -> Option<Self> {
match (row_option, column_option) {
(Some(row), Some(column)) => Some(Self::new(row, column)),
_ => None,
}
}
}
impl<T> Index<CellLocation> for Array2<T> {
type Output = T;
fn index<'a>(&'a self, cell_location: CellLocation) -> &'a T {
&self[[cell_location.row, cell_location.column]]
}
}
impl<T> IndexMut<CellLocation> for Array2<T> {
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<CellLocation> {
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<T: Default>(&self) -> Array2<T> {
Array2::default((self.rows, self.columns))
}
pub fn create_board_from_shape_fn<T, F: Fn(CellLocation) -> T>(&self, f: F) -> Array2<T> {
Array2::from_shape_fn((self.rows, self.columns), |(row, column)| f(CellLocation::new(row, column)))
}
}

@ -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<Direction, Option<CellLocation>>,
state: CellState,
}
impl CellInfo {
fn new(neighbours: EnumMap<Direction, Option<CellLocation>>) -> 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<State>,
board: Array2<CellInfo>,
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<T: Rules>() -> Vec<State> {
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<R: Rules>(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::<Vec<_>>();
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::<R>();
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::<String>());
}
}
pub fn get_count_of_cells_for_state(&self, state: State) -> usize {
(&self.board).iter().filter(|&cell| cell.state.get_state() == state).count()
}
}

@ -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<u32>,
board: Array2<u32>,
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<T: StateRules>(state_rules: T) -> Vec<u32> {
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<T: StateRules>(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::<Vec<_>>();
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::<String>());
}
}
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::<RulesEasy>(&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));
}

@ -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, usize>) -> State;
fn get_neighbours(cell_location: CellLocation, board_metadata: &BoardMetadata, original_states: &Array2<State>) -> EnumMap<Direction, Option<CellLocation>>;
}

@ -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, usize>) -> 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<State>) -> EnumMap<Direction, Option<CellLocation>> {
let mut neighbours = EnumMap::new();
for direction in Direction::iter() {
neighbours[direction] = board_metadata.get_neighbour_location(cell_location, direction);
}
neighbours
}
}
Loading…
Cancel
Save