Cleanup for 2022 day 9: Turned into a lib and introduced parse errors

This commit is contained in:
Burnus 2023-05-14 14:18:15 +02:00
parent c030374ccf
commit 6e1656f7ee
3 changed files with 121 additions and 101 deletions

View file

@ -0,0 +1,121 @@
use core::fmt::Display;
use std::num::ParseIntError;
use std::collections::HashSet;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
ParseIntError(std::num::ParseIntError),
UnexpectedDirection(&'a str),
}
impl From<ParseIntError> for ParseError<'_> {
fn from(value: ParseIntError) -> Self {
Self::ParseIntError(value)
}
}
impl Display for ParseError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
Self::UnexpectedDirection(c) => write!(f, "Trying to parse unexpected character {c} into direction"),
}
}
}
enum Direction {
Left,
Right,
Up,
Down,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
struct Position {
x: i32,
y: i32,
}
impl Position {
fn perform_motion(&mut self, direction: &Direction) {
match direction {
Direction::Left => self.x-=1,
Direction::Right => self.x+=1,
Direction::Up => self.y+=1,
Direction::Down => self.y-=1,
}
}
fn follow(&self, head: &Position) -> Self {
let (dx, dy) = (head.x-self.x, head.y-self.y);
match (dx, dy) {
(2,0) => Self { x: self.x+1, y: self.y },
(0,2) => Self { x: self.x, y: self.y+1 },
(-2,0) => Self { x: self.x-1, y: self.y },
(0,-2) => Self { x: self.x, y: self.y-1},
(2,2)| (2,1) | (1,2) => Self { x: self.x+1, y: self.y+1 },
(2,-2) | (2,-1) | (1,-2) => Self { x: self.x+1, y: self.y-1 },
(-2,-2) | (-2,-1) | (-1,-2) => Self { x: self.x-1, y: self.y-1 },
(-2,2) | (-2,1) |(-1,2) => Self { x: self.x-1, y: self.y+1 },
_ => *self,
}
}
}
fn parse_head_movement(instruction: &str) -> Result<(Direction, i32), ParseError> {
let direction = match &instruction[0..=0] {
"L" => Ok(Direction::Left),
"R" => Ok(Direction::Right),
"U" => Ok(Direction::Up),
"D" => Ok(Direction::Down),
e => Err(ParseError::UnexpectedDirection(e)),
}?;
let count = instruction[2..].parse()?;
Ok((direction, count))
}
fn get_visited(head_movements: &[(Direction, i32)], rope_length: usize) -> HashSet<Position> {
let mut positions = vec![Position { x:0, y: 0 }; rope_length];
let mut visited = HashSet::new();
visited.insert(positions[0]);
for (head_direction, count) in head_movements {
for _ in 0..*count {
positions[0].perform_motion(head_direction);
for i in 1..rope_length {
positions[i] = positions[i].follow(&positions[i-1]);
}
visited.insert(positions[rope_length-1]);
}
}
visited
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let movements: Vec<_> = input.lines().map(parse_head_movement).collect::<Result<Vec<_>, _>>()?;
let first = get_visited(&movements, 2).len();
let second = get_visited(&movements, 10).len();
Ok((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((13, 1)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((6376, 2607)));
}
}

View file

@ -1,101 +0,0 @@
use std::fs;
use std::collections::HashSet;
enum Direction {
Left,
Right,
Up,
Down,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
struct Position {
x: i32,
y: i32,
}
impl Position {
fn perform_motion(&mut self, direction: &Direction) {
match direction {
Direction::Left => self.x-=1,
Direction::Right => self.x+=1,
Direction::Up => self.y+=1,
Direction::Down => self.y-=1,
}
}
fn follow(&self, head: &Position) -> Self {
let (dx, dy) = (head.x-self.x, head.y-self.y);
match (dx, dy) {
(2,0) => Self { x: self.x+1, y: self.y },
(0,2) => Self { x: self.x, y: self.y+1 },
(-2,0) => Self { x: self.x-1, y: self.y },
(0,-2) => Self { x: self.x, y: self.y-1},
(2,2)| (2,1) | (1,2) => Self { x: self.x+1, y: self.y+1 },
(2,-2) | (2,-1) | (1,-2) => Self { x: self.x+1, y: self.y-1 },
(-2,-2) | (-2,-1) | (-1,-2) => Self { x: self.x-1, y: self.y-1 },
(-2,2) | (-2,1) |(-1,2) => Self { x: self.x-1, y: self.y+1 },
_ => *self,
}
}
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn parse_head_movement(instruction: &str) -> (Direction, i32) {
let direction = match &instruction[0..=0] {
"L" => Direction::Left,
"R" => Direction::Right,
"U" => Direction::Up,
"D" => Direction::Down,
_ => panic!("Unknown Direction"),
};
let count = instruction[2..].parse().unwrap();
(direction, count)
}
fn get_visited(head_movements: &str, rope_length: usize) -> HashSet<Position> {
let mut positions = vec![Position { x:0, y: 0 }; rope_length];
let mut visited = HashSet::new();
visited.insert(positions[0]);
for instruction in head_movements.lines() {
let (head_direction, count) = parse_head_movement(instruction);
for _ in 0..count {
positions[0].perform_motion(&head_direction);
for i in 1..rope_length {
positions[i] = positions[i].follow(&positions[i-1]);
}
visited.insert(positions[rope_length-1]);
}
}
visited
}
fn main() {
//let head_movements = read_file("sample_input");
let head_movements = read_file("input");
let visited_fixed = get_visited(&head_movements, 2);
let visited_loose = get_visited(&head_movements, 10);
println!("The fixed tail visited a total of {} positions.", visited_fixed.len());
println!("The loose tail visited a total of {} positions.", visited_loose.len());
}
#[test]
fn sample_input() {
let head_movements = read_file("tests/sample_input");
assert_eq!(get_visited(&head_movements, 2).len(), 13);
assert_eq!(get_visited(&head_movements, 10).len(), 1);
}
#[test]
fn challenge_input() {
let head_movements = read_file("tests/input");
assert_eq!(get_visited(&head_movements, 2).len(), 6376);
assert_eq!(get_visited(&head_movements, 10).len(), 2607);
}