diff --git a/day17/Cargo.toml b/day17/Cargo.toml new file mode 100644 index 0000000..72f0f46 --- /dev/null +++ b/day17/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day17" +version = "0.1.0" +authors = ["inga-lovinde <52715130+inga-lovinde@users.noreply.github.com>"] +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/day17/src/binary.rs b/day17/src/binary.rs new file mode 100644 index 0000000..48e6a5a --- /dev/null +++ b/day17/src/binary.rs @@ -0,0 +1,105 @@ +use enum_map::Enum; +use strum_macros::EnumIter; + +#[derive(Clone, Copy, Enum, Eq, PartialEq)] +pub enum State { + Dead = 0, + Alive = 1, +} + +impl Default for State { + fn default() -> Self { Self::Dead } +} + +impl State { + fn from_number(number: u8) -> Self { + match number { + 0 => Self::Dead, + 1 => Self::Alive, + _ => panic!("unsupported number {}", number), + } + } + + fn get_number(&self) -> u8 { + *self as u8 + } +} + +// terribly error-prone but I'm lazy :( +#[derive(Copy, Clone, Enum, EnumIter)] +pub enum Direction { + MinusMinusMinus = 0, + MinusMinusSame = 1, + MinusMinusPlus = 2, + MinusSameMinus = 3, + MinusSameSame = 4, + MinusSamePlus = 5, + MinusPlusMinus = 6, + MinusPlusSame = 7, + MinusPlusPlus = 8, + SameMinusMinus = 9, + SameMinusSame = 10, + SameMinusPlus = 11, + SameSameMinus = 12, + SameSamePlus = 13, + SamePlusMinus = 14, + SamePlusSame = 15, + SamePlusPlus = 16, + PlusMinusMinus = 17, + PlusMinusSame = 18, + PlusMinusPlus = 19, + PlusSameMinus = 20, + PlusSameSame = 21, + PlusSamePlus = 22, + PlusPlusMinus = 23, + PlusPlusSame = 24, + PlusPlusPlus = 25, +} + +impl Direction { + fn get_offset(&self) -> u16 { + *self as u16 + } +} + +pub struct CellState { + neighbours_states: u32, + 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 & !(0b1 << direction.get_offset())) | ((new_state.get_number() as u32) << 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 >> 26) as u8, + neighbours_states: (number & 0x3ffffff) as u32, + } + } + + pub fn get_number(&self) -> u32 { + ((self.state as u32) << 26) | (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()) & 0b1) as u8) + } +} + diff --git a/day17/src/board_metadata.rs b/day17/src/board_metadata.rs new file mode 100644 index 0000000..2a1e969 --- /dev/null +++ b/day17/src/board_metadata.rs @@ -0,0 +1,148 @@ +use std::default::Default; +use std::ops::{Index, IndexMut}; +use ndarray::Array3; + +use crate::binary::Direction; + +#[derive(Copy, Clone)] +pub struct CellLocation { + pub x: usize, + pub y: usize, + pub z: usize, +} + +impl CellLocation { + pub fn new(x: usize, y: usize, z: usize) -> Self { + Self { + x, + y, + z, + } + } + + fn new_option(x_option: Option, y_option: Option, z_option: Option) -> Option { + match (x_option, y_option, z_option) { + (Some(x), Some(y), Some(z)) => Some(Self::new(x, y, z)), + _ => None, + } + } +} + +impl Index for Array3 { + type Output = T; + fn index<'a>(&'a self, cell_location: CellLocation) -> &'a T { + &self[[cell_location.x, cell_location.y, cell_location.z]] + } +} + +impl IndexMut for Array3 { + fn index_mut<'a>(&'a mut self, cell_location: CellLocation) -> &'a mut T { + &mut self[[cell_location.x, cell_location.y, cell_location.z]] + } +} + +pub struct BoardMetadata { + corner: CellLocation, +} + +struct CellLocationWithBoardMetadata { + corner: CellLocation, + location: Option, +} + +impl CellLocationWithBoardMetadata { + fn new(corner: CellLocation, location: Option) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata { + corner, + location, + } + } + + fn get_x_minus(&self) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata::new(self.corner, match self.location { + Some(location) if location.x > 0 => Some(CellLocation { x: location.x-1, ..location }), + _ => None + }) + } + + fn get_x_plus(&self) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata::new(self.corner, match self.location { + Some(location) if location.x < self.corner.x - 1 => Some(CellLocation { x: location.x+1, ..location }), + _ => None + }) + } + + fn get_y_minus(&self) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata::new(self.corner, match self.location { + Some(location) if location.y > 0 => Some(CellLocation { y: location.y-1, ..location }), + _ => None + }) + } + + fn get_y_plus(&self) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata::new(self.corner, match self.location { + Some(location) if location.y < self.corner.y - 1 => Some(CellLocation { y: location.y+1, ..location }), + _ => None + }) + } + + fn get_z_minus(&self) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata::new(self.corner, match self.location { + Some(location) if location.z > 0 => Some(CellLocation { z: location.z-1, ..location }), + _ => None + }) + } + + fn get_z_plus(&self) -> CellLocationWithBoardMetadata { + CellLocationWithBoardMetadata::new(self.corner, match self.location { + Some(location) if location.z < self.corner.z - 1 => Some(CellLocation { z: location.z+1, ..location }), + _ => None + }) + } +} + +impl BoardMetadata { + pub fn new(corner: CellLocation) -> Self { + BoardMetadata { + corner, + } + } + + pub fn get_neighbour_location(&self, cell_location: CellLocation, direction: Direction) -> Option { + let cell_with_metadata = CellLocationWithBoardMetadata::new(self.corner, Some(cell_location)); + + // terribly error-prone but I'm lazy :( + match direction { + Direction::MinusMinusMinus => cell_with_metadata.get_x_minus().get_y_minus().get_z_minus().location, + Direction::MinusMinusSame => cell_with_metadata.get_x_minus().get_y_minus().location, + Direction::MinusMinusPlus => cell_with_metadata.get_x_minus().get_y_minus().get_z_plus().location, + Direction::MinusSameMinus => cell_with_metadata.get_x_minus().get_z_minus().location, + Direction::MinusSameSame => cell_with_metadata.get_x_minus().location, + Direction::MinusSamePlus => cell_with_metadata.get_x_minus().get_z_plus().location, + Direction::MinusPlusMinus => cell_with_metadata.get_x_minus().get_y_plus().get_z_minus().location, + Direction::MinusPlusSame => cell_with_metadata.get_x_minus().get_y_plus().location, + Direction::MinusPlusPlus => cell_with_metadata.get_x_minus().get_y_plus().get_z_plus().location, + Direction::SameMinusMinus => cell_with_metadata.get_y_minus().get_z_minus().location, + Direction::SameMinusSame => cell_with_metadata.get_y_minus().location, + Direction::SameMinusPlus => cell_with_metadata.get_y_minus().get_z_plus().location, + Direction::SameSameMinus => cell_with_metadata.get_z_minus().location, + Direction::SameSamePlus => cell_with_metadata.get_z_plus().location, + Direction::SamePlusMinus => cell_with_metadata.get_y_plus().get_z_minus().location, + Direction::SamePlusSame => cell_with_metadata.get_y_plus().location, + Direction::SamePlusPlus => cell_with_metadata.get_y_plus().get_z_plus().location, + Direction::PlusMinusMinus => cell_with_metadata.get_x_plus().get_y_minus().get_z_minus().location, + Direction::PlusMinusSame => cell_with_metadata.get_x_plus().get_y_minus().location, + Direction::PlusMinusPlus => cell_with_metadata.get_x_plus().get_y_minus().get_z_plus().location, + Direction::PlusSameMinus => cell_with_metadata.get_x_plus().get_z_minus().location, + Direction::PlusSameSame => cell_with_metadata.get_x_plus().location, + Direction::PlusSamePlus => cell_with_metadata.get_x_plus().get_z_plus().location, + Direction::PlusPlusMinus => cell_with_metadata.get_x_plus().get_y_plus().get_z_minus().location, + Direction::PlusPlusSame => cell_with_metadata.get_x_plus().get_y_plus().location, + Direction::PlusPlusPlus => cell_with_metadata.get_x_plus().get_y_plus().get_z_plus().location, + } + } + + pub fn create_board_from_shape_fn T>(&self, f: F) -> Array3 { + Array3::from_shape_fn((self.corner.x, self.corner.y, self.corner.z), |(x, y, z)| f(CellLocation::new(x, y, z))) + } +} diff --git a/day17/src/game.rs b/day17/src/game.rs new file mode 100644 index 0000000..9c1c62f --- /dev/null +++ b/day17/src/game.rs @@ -0,0 +1,163 @@ +use enum_map::EnumMap; +use ndarray::{Array2, Array3}; +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; + +const RESERVE: usize = 10; + +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: Array3, + corner: CellLocation, +} + +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::Dead; 1 << 27]; + + for i in 0..1usize << 27 { + 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 x in 0..self.corner.x { + for y in 0..self.corner.y { + for z in 0..self.corner.z { + let location = CellLocation::new(x, y, z); + 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 mut states = Array2::default((rows, columns)); + + 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::Dead, + '#' => State::Alive, + _ => panic!("unsupported state"), + } + } + } + + let corner = CellLocation::new(rows + 2*RESERVE, columns + 2*RESERVE, 1 + 2*RESERVE); + let board_metadata = BoardMetadata::new(corner); + 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 { + board, + cell_rules, + corner, + }; + + for row in 0..rows { + for column in 0..columns { + let location = CellLocation::new(row + RESERVE, column + RESERVE, RESERVE); + game.update_cell(location, states[(row, column)]); + } + } + + 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/day17/src/main.rs b/day17/src/main.rs new file mode 100644 index 0000000..1a4b6a5 --- /dev/null +++ b/day17/src/main.rs @@ -0,0 +1,39 @@ +#![feature(trait_alias)] + +use std::io::{self, BufRead}; + +mod binary; +mod board_metadata; +mod game; +mod rules; +mod rules_easy; + +use binary::State; +use game::Game; +use rules::Rules; +use rules_easy::RulesEasy; + +fn solve(lines: &[String]) { + let mut game = Game::from_input::(&lines); + + //game.print_board(); + + for i in 1.. { + let changes_count = game.next_step(); + println!("Iteration {}; changed cells: {}; alive_cells: {}", i, changes_count, game.get_count_of_cells_for_state(State::Alive)); + //game.print_board(); + if changes_count == 0 { + break; + } + } + + //game.print_board(); + println!("Board stabilized at {} occupied seats", game.get_count_of_cells_for_state(State::Alive)); +} + +fn main() { + let stdin = io::stdin(); + let lines: Vec<_> = stdin.lock().lines().map(|line| line.unwrap()).collect(); + + solve::(&lines); +} diff --git a/day17/src/rules.rs b/day17/src/rules.rs new file mode 100644 index 0000000..eb9dcec --- /dev/null +++ b/day17/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/day17/src/rules_easy.rs b/day17/src/rules_easy.rs new file mode 100644 index 0000000..60e9fb0 --- /dev/null +++ b/day17/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::Alive if neighbour_counts[State::Alive] == 2 => State::Alive, + _ if neighbour_counts[State::Alive] == 3 => State::Alive, + _ => State::Dead, + } + } + + 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 + } +}