From 7a120073f9f1f43b075370e03e3043c3aaddfc27 Mon Sep 17 00:00:00 2001 From: Lawrence Niu Date: Sat, 24 Sep 2022 01:23:06 -0400 Subject: [PATCH] completed day 5 part 1 --- src/bingo.rs | 83 -------------------------------- src/canvas.rs | 32 +++++++++++++ src/draw.rs | 20 -------- src/geometry.rs | 122 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 57 +++++----------------- 5 files changed, 166 insertions(+), 148 deletions(-) delete mode 100644 src/bingo.rs create mode 100644 src/canvas.rs delete mode 100644 src/draw.rs create mode 100644 src/geometry.rs diff --git a/src/bingo.rs b/src/bingo.rs deleted file mode 100644 index e3ca77d..0000000 --- a/src/bingo.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::convert; -use std::error; -use std::fmt; - -#[derive(Debug)] -pub struct TryFromSliceError(String); - -impl fmt::Display for TryFromSliceError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "error generating board from slice ({})", self.0) - } -} - -impl error::Error for TryFromSliceError {} - -pub struct Board { - buf: [u32; 25], - is_marked_buf: [bool; 25], -} - -impl convert::TryFrom<&[u32]> for Board { - type Error = TryFromSliceError; - - fn try_from(slice: &[u32]) -> Result { - let buf = slice - .get(..25) - .ok_or(TryFromSliceError(format!( - "could not convert from slice. Len must be >= 25, received len {}", - slice.len() - ))) - .and_then(|slice| { - <[u32; 25]>::try_from(slice) - .map_err(|from_slice_err| TryFromSliceError(from_slice_err.to_string())) - })?; - - Ok(Board { - buf, - is_marked_buf: [false; 25], - }) - } -} - -impl Board { - pub fn mark(&mut self, value: u32) { - self.buf - .iter() - .copied() - .zip(self.is_marked_buf.iter_mut()) - .filter(|(v, _)| *v == value) - .for_each(|(_, is_marked)| { - *is_marked = true; - }); - } - - pub fn score(&self) -> u32 { - self.buf - .iter() - .copied() - .zip(self.is_marked_buf.iter().copied()) - .map(|(value, is_marked)| if is_marked { 0u32 } else { value }) - .sum() - } - - pub fn has_won(&self) -> bool { - // diagonal - let is_row_win = self - .is_marked_buf - .chunks(5) - .any(|row| row.iter().copied().all(|is_marked| is_marked)); - - #[rustfmt::skip] - let is_col_win = [ - 0usize, 5, 10, 15, 20, - 1, 6, 11, 16, 21, - 2, 7, 12, 17, 22, - 3, 8, 13, 18, 23, - 4, 9, 14, 19, 24, - ] - .chunks(5) - .any(|i_col| i_col.iter().copied().all(|i| self.is_marked_buf[i])); - is_row_win || is_col_win - } -} diff --git a/src/canvas.rs b/src/canvas.rs new file mode 100644 index 0000000..2dbf007 --- /dev/null +++ b/src/canvas.rs @@ -0,0 +1,32 @@ +use crate::geometry; +use std::collections::hash_map; +use std::collections::HashMap; + +pub struct Canvas { + mapping: HashMap, +} + +impl Canvas { + pub fn new() -> Self { + Canvas { + mapping: HashMap::new(), + } + } + + fn count_point(&mut self, point: geometry::Point) { + self.mapping + .entry(point) + .and_modify(|count| { + *count += 1; + }) + .or_insert(1); + } + + pub fn count_line(&mut self, line: &geometry::Line) { + line.trace().into_iter().for_each(|p| self.count_point(p)); + } + + pub fn iter(&self) -> hash_map::Iter<'_, geometry::Point, u32> { + self.mapping.iter() + } +} diff --git a/src/draw.rs b/src/draw.rs deleted file mode 100644 index 8a9e610..0000000 --- a/src/draw.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::convert; -use std::error; -use std::fmt; - -#[derive(Debug)] -pub struct ParseDrawError(String); - -impl fmt::Display for ParseDrawError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "error parsing card drawer ({})", self.0) - } -} - -impl error::Error for ParseDrawError {} - -impl convert::From<&str> for ParseDrawError { - fn from(s: &str) -> Self { - ParseDrawError(format!("{}", s)) - } -} diff --git a/src/geometry.rs b/src/geometry.rs new file mode 100644 index 0000000..8d3a243 --- /dev/null +++ b/src/geometry.rs @@ -0,0 +1,122 @@ +use std::cmp; +use std::error; +use std::fmt; +use std::str; + +#[derive(Debug)] +pub struct ParsePointError(String); + +impl fmt::Display for ParsePointError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to parse point from string.\n {}", self.0) + } +} + +impl error::Error for ParsePointError {} + +#[derive(Eq, Hash, PartialEq)] +pub struct Point { + pub x: u32, + pub y: u32, +} + +impl str::FromStr for Point { + type Err = ParsePointError; + + fn from_str(s: &str) -> Result { + let mut tokens = s.split(','); + let x = tokens + .next() + .ok_or(ParsePointError(format!("missing x in \"x,y\""))) + .and_then(|x_str| { + x_str.trim().parse().map_err(|parse_err| { + ParsePointError(format!("failed to parse x.\n {}", parse_err)) + }) + })?; + let y = tokens + .next() + .ok_or(ParsePointError(format!("missing y in \"x,y\""))) + .and_then(|y_str| { + y_str.trim().parse().map_err(|parse_err| { + ParsePointError(format!("failed to parse y.\n {}", parse_err)) + }) + })?; + Ok(Point { x, y }) + } +} + +#[derive(Debug)] +pub struct ParseLineError(String); + +impl fmt::Display for ParseLineError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to parse line from string.\n {}", self.0) + } +} + +impl error::Error for ParseLineError {} + +pub struct Line { + pub from: Point, + pub to: Point, +} + +impl str::FromStr for Line { + type Err = ParseLineError; + + fn from_str(s: &str) -> Result { + let mut tokens = s.split_whitespace(); + let from = tokens + .next() + .ok_or(ParseLineError(format!("could not find \"from\" point."))) + .and_then(|from_str| { + from_str.parse().map_err(|parse_err| { + ParseLineError(format!("could not parse \"from\" point.\n {}", parse_err)) + }) + })?; + tokens.next(); // -> symbol which is ignored + let to = tokens + .next() + .ok_or(ParseLineError(format!("could not find \"to\" point."))) + .and_then(|to_str| { + to_str.parse().map_err(|parse_err| { + ParseLineError(format!("could not parse \"to\" point.\n {}", parse_err)) + }) + })?; + Ok(Line { from, to }) + } +} + +impl Line { + pub fn is_diagonal(&self) -> bool { + self.from.x != self.to.x && self.from.y != self.to.y + } + + pub fn trace(&self) -> Vec { + if self.is_diagonal() { + unimplemented!(); + } + let is_vertical = self.from.x == self.to.x; + let ((a, b), pivot_value) = if is_vertical { + ((self.from.y, self.to.y), self.from.x) + } else { + ((self.from.x, self.to.x), self.from.y) + }; + + (cmp::min(a, b)..=cmp::max(a, b)) + .map(|value| { + if is_vertical { + Point { + x: pivot_value, + y: value, + } + } else { + Point { + x: value, + y: pivot_value, + } + } + }) + .collect() + } +} diff --git a/src/main.rs b/src/main.rs index 5f23e2b..152fb05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,54 +1,21 @@ -mod bingo; -mod draw; +mod canvas; +mod geometry; use anyhow::Result; use std::fs::File; use std::io::{BufRead, BufReader}; use std::str; -fn play_bingo(draws: &[u32], mut boards: Vec) -> u64 { - for v in draws.iter().copied() { - boards.iter_mut().for_each(|b| b.mark(v)); - if 1 == boards.len() { - if boards[0].has_won() { - return (boards[0].score() as u64) * (v as u64); - } - } else { - boards = boards.into_iter().filter(|b| !b.has_won()).collect(); +fn process_lines(reader: impl BufRead) -> Result { + let mut board = canvas::Canvas::new(); + for line_res in reader.lines() { + let line_str = line_res?; + let line: geometry::Line = line_str.parse()?; + if !line.is_diagonal() { + board.count_line(&line); } } - unreachable!(); -} - -fn process_lines(reader: impl BufRead) -> Result<(Vec, Vec)> { - let mut lines_iter = reader.lines(); - let draw_str = lines_iter.next().ok_or(draw::ParseDrawError::from( - "could not find draw buffer in file.", - ))??; - - let draw_buffer: Vec = draw_str - .split(',') - .map(|token| token.parse::()) - .collect::, _>>()?; - - let mut boards: Vec = Vec::new(); - let mut board_buffer: Vec = Vec::new(); - lines_iter.next(); // ignore first empty line - for lines_res in lines_iter { - let line = lines_res?; - if line.is_empty() { - boards.push(bingo::Board::try_from(&board_buffer[..])?); - board_buffer.clear(); - } else { - for value in line.split_whitespace() { - board_buffer.push(value.parse()?); - } - } - } - //the last board line is not delimited by an empty space. - boards.push(bingo::Board::try_from(&board_buffer[..])?); - - Ok((draw_buffer, boards)) + Ok(board.iter().filter(|(_, counts)| 1 < **counts).count() as u64) } fn main() { const INPUT_PATH: &str = "data/input.txt"; @@ -58,8 +25,8 @@ fn main() { Err(err) => { eprintln!("Could not process file {}:\n {}", INPUT_PATH, err); } - Ok((draws, boards)) => { - println!("Final score: {}", play_bingo(&draws[..], boards)); + Ok(count) => { + println!("# overlapping points: {}", count); } }, Err(err) => {