Solution for day 17 part 1 (adapted from day 11)

main
Inga 🏳‍🌈 3 years ago
parent 59f78b9069
commit dc69aea676
  1. 13
      day17/Cargo.toml
  2. 105
      day17/src/binary.rs
  3. 148
      day17/src/board_metadata.rs
  4. 163
      day17/src/game.rs
  5. 39
      day17/src/main.rs
  6. 9
      day17/src/rules.rs
  7. 28
      day17/src/rules_easy.rs

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

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

@ -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<usize>, y_option: Option<usize>, z_option: Option<usize>) -> Option<Self> {
match (x_option, y_option, z_option) {
(Some(x), Some(y), Some(z)) => Some(Self::new(x, y, z)),
_ => None,
}
}
}
impl<T> Index<CellLocation> for Array3<T> {
type Output = T;
fn index<'a>(&'a self, cell_location: CellLocation) -> &'a T {
&self[[cell_location.x, cell_location.y, cell_location.z]]
}
}
impl<T> IndexMut<CellLocation> for Array3<T> {
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<CellLocation>,
}
impl CellLocationWithBoardMetadata {
fn new(corner: CellLocation, location: Option<CellLocation>) -> 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<CellLocation> {
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, F: Fn(CellLocation) -> T>(&self, f: F) -> Array3<T> {
Array3::from_shape_fn((self.corner.x, self.corner.y, self.corner.z), |(x, y, z)| f(CellLocation::new(x, y, z)))
}
}

@ -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<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: Array3<CellInfo>,
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<T: Rules>() -> Vec<State> {
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<R: Rules>(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::<Vec<_>>();
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::<R>();
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::<String>());
}
}*/
pub fn get_count_of_cells_for_state(&self, state: State) -> usize {
(&self.board).iter().filter(|&cell| cell.state.get_state() == state).count()
}
}

@ -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<T: Rules>(lines: &[String]) {
let mut game = Game::from_input::<T>(&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::<RulesEasy>(&lines);
}

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