Files
adventofcode2024/day_04/src/crossword.rs
2025-05-29 22:02:50 -05:00

344 lines
11 KiB
Rust

use std::error::Error;
use std::fmt::Display;
use std::fs::File;
use std::io::{BufReader, prelude::*};
#[derive(Debug)]
pub struct Crossword {
data: Vec<Vec<char>>,
width: usize,
height: usize,
}
pub struct CrosswordCell<'a> {
x: usize,
y: usize,
crossword: &'a Crossword,
}
impl<'a> CrosswordCell<'a> {
fn value_by_index(&self, x: usize, y: usize) -> char {
// x and y are reverse because we store by row
//
// data[ROW][COLUMN]
self.crossword.data[y][x]
}
pub fn value(&self) -> char {
self.value_by_index(self.x, self.y)
}
fn take(&self, num_of_letters: usize, direction: (isize, isize), start: isize) -> String {
let mut word = String::new();
let end = start + num_of_letters as isize;
for i in start..end {
let x = self.x as isize + (i * direction.1);
let y = self.y as isize + (i * direction.0);
if x < 0
|| x >= self.crossword.width as isize
|| y < 0
|| y >= self.crossword.height as isize
{
if i < 0 {
continue;
} else {
break;
}
}
word.push(self.value_by_index(x as usize, y as usize));
}
word
}
const DIRECTION_N: (isize, isize) = (-1, 0);
pub fn n(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_N, 0)
}
pub fn n2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_N, start)
}
const DIRECTION_NE: (isize, isize) = (-1, 1);
pub fn ne(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_NE, 0)
}
pub fn ne2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_NE, start)
}
const DIRECTION_E: (isize, isize) = (0, 1);
pub fn e(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_E, 0)
}
pub fn e2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_E, start)
}
const DIRECTION_SE: (isize, isize) = (1, 1);
pub fn se(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_SE, 0)
}
pub fn se2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_SE, start)
}
const DIRECTION_S: (isize, isize) = (1, 0);
pub fn s(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_S, 0)
}
pub fn s2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_S, start)
}
const DIRECTION_SW: (isize, isize) = (1, -1);
pub fn sw(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_SW, 0)
}
pub fn sw2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_SW, start)
}
const DIRECTION_W: (isize, isize) = (0, -1);
pub fn w(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_W, 0)
}
pub fn w2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_W, start)
}
const DIRECTION_NW: (isize, isize) = (-1, -1);
pub fn nw(&self, num_of_letters: usize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_NW, 0)
}
pub fn nw2(&self, num_of_letters: usize, start: isize) -> String {
self.take(num_of_letters, CrosswordCell::DIRECTION_NW, start)
}
}
pub struct CrosswordIterator<'a> {
crossword: &'a Crossword,
position: usize,
}
impl<'a> Iterator for CrosswordIterator<'a> {
type Item = CrosswordCell<'a>;
fn next(&mut self) -> Option<Self::Item> {
let y = self.position / self.crossword.width;
let x = self.position - y * self.crossword.width;
self.position += 1;
if x < self.crossword.width && y < self.crossword.height {
Some(CrosswordCell {
x,
y,
crossword: self.crossword,
})
} else {
None
}
}
}
impl Crossword {
pub fn new(file: File) -> Result<Crossword, Box<dyn Error>> {
let mut input_buffer = BufReader::new(file);
let mut data: Vec<Vec<char>> = Vec::new();
let mut first_line = String::new();
input_buffer.read_line(&mut first_line)?;
let first_line = first_line.trim_end().to_owned();
let width = first_line.len();
data.push(first_line.chars().collect());
for line in input_buffer.lines() {
let line = line?;
if line.len() != width {
println!("{}", line);
Err("Line length of input are not consistent")?;
}
data.push(line.chars().collect());
}
let height = data.len();
Ok(Crossword {
data,
width,
height,
})
}
fn from_data(data: Vec<Vec<char>>) -> Crossword {
let height = data.len();
let width = data[0].len();
Crossword {
data,
width,
height,
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn iter_cells(&self) -> CrosswordIterator {
CrosswordIterator {
crossword: self,
position: 0,
}
}
pub fn cell(&self, x: usize, y: usize) -> CrosswordCell {
CrosswordCell {
x,
y,
crossword: self,
}
}
}
impl Display for Crossword {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for row in &self.data {
let row = String::from_iter(row.iter());
writeln!(f, "{}", row)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
fn small_test_crossword() -> Crossword {
Crossword::from_data(vec![
vec!['1', '2', '3'],
vec!['4', '5', '6'],
vec!['7', '8', '9'],
])
}
#[test]
fn test_north() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).n(0), "");
assert_eq!(crossword.cell(1, 1).n(1), "5");
assert_eq!(crossword.cell(1, 1).n(2), "52");
assert_eq!(crossword.cell(1, 1).n(3), "52");
assert_eq!(crossword.cell(1, 1).n2(3, 1), "2");
assert_eq!(crossword.cell(1, 1).n2(3, 0), "52");
assert_eq!(crossword.cell(1, 1).n2(3, -1), "852");
assert_eq!(crossword.cell(1, 1).n2(3, -2), "85");
}
#[test]
fn test_northeast() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).ne(0), "");
assert_eq!(crossword.cell(1, 1).ne(1), "5");
assert_eq!(crossword.cell(1, 1).ne(2), "53");
assert_eq!(crossword.cell(1, 1).ne(3), "53");
assert_eq!(crossword.cell(1, 1).ne2(3, 1), "3");
assert_eq!(crossword.cell(1, 1).ne2(3, 0), "53");
assert_eq!(crossword.cell(1, 1).ne2(3, -1), "753");
assert_eq!(crossword.cell(1, 1).ne2(3, -2), "75");
}
#[test]
fn test_east() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).e(0), "");
assert_eq!(crossword.cell(1, 1).e(1), "5");
assert_eq!(crossword.cell(1, 1).e(2), "56");
assert_eq!(crossword.cell(1, 1).e(3), "56");
assert_eq!(crossword.cell(1, 1).e2(3, 1), "6");
assert_eq!(crossword.cell(1, 1).e2(3, 0), "56");
assert_eq!(crossword.cell(1, 1).e2(3, -1), "456");
assert_eq!(crossword.cell(1, 1).e2(3, -2), "45");
}
#[test]
fn test_southeast() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).se(0), "");
assert_eq!(crossword.cell(1, 1).se(1), "5");
assert_eq!(crossword.cell(1, 1).se(2), "59");
assert_eq!(crossword.cell(1, 1).se(3), "59");
assert_eq!(crossword.cell(1, 1).se2(3, 1), "9");
assert_eq!(crossword.cell(1, 1).se2(3, 0), "59");
assert_eq!(crossword.cell(1, 1).se2(3, -1), "159");
assert_eq!(crossword.cell(1, 1).se2(3, -2), "15");
}
#[test]
fn test_south() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).s(0), "");
assert_eq!(crossword.cell(1, 1).s(1), "5");
assert_eq!(crossword.cell(1, 1).s(2), "58");
assert_eq!(crossword.cell(1, 1).s(3), "58");
assert_eq!(crossword.cell(1, 1).s2(3, 1), "8");
assert_eq!(crossword.cell(1, 1).s2(3, 0), "58");
assert_eq!(crossword.cell(1, 1).s2(3, -1), "258");
assert_eq!(crossword.cell(1, 1).s2(3, -2), "25");
}
#[test]
fn test_southwest() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).sw(0), "");
assert_eq!(crossword.cell(1, 1).sw(1), "5");
assert_eq!(crossword.cell(1, 1).sw(2), "57");
assert_eq!(crossword.cell(1, 1).sw(3), "57");
assert_eq!(crossword.cell(1, 1).sw2(3, 1), "7");
assert_eq!(crossword.cell(1, 1).sw2(3, 0), "57");
assert_eq!(crossword.cell(1, 1).sw2(3, -1), "357");
assert_eq!(crossword.cell(1, 1).sw2(3, -2), "35");
}
#[test]
fn test_west() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).w(0), "");
assert_eq!(crossword.cell(1, 1).w(1), "5");
assert_eq!(crossword.cell(1, 1).w(2), "54");
assert_eq!(crossword.cell(1, 1).w(3), "54");
assert_eq!(crossword.cell(1, 1).w2(3, 1), "4");
assert_eq!(crossword.cell(1, 1).w2(3, 0), "54");
assert_eq!(crossword.cell(1, 1).w2(3, -1), "654");
assert_eq!(crossword.cell(1, 1).w2(3, -2), "65");
}
#[test]
fn test_northwest() {
let crossword = small_test_crossword();
assert_eq!(crossword.cell(1, 1).nw(0), "");
assert_eq!(crossword.cell(1, 1).nw(1), "5");
assert_eq!(crossword.cell(1, 1).nw(2), "51");
assert_eq!(crossword.cell(1, 1).nw(3), "51");
assert_eq!(crossword.cell(1, 1).nw2(3, 1), "1");
assert_eq!(crossword.cell(1, 1).nw2(3, 0), "51");
assert_eq!(crossword.cell(1, 1).nw2(3, -1), "951");
assert_eq!(crossword.cell(1, 1).nw2(3, -2), "95");
}
}