diff --git a/2022/day22-monkey_map/src/main.rs b/2022/day22-monkey_map/src/lib.rs similarity index 78% rename from 2022/day22-monkey_map/src/main.rs rename to 2022/day22-monkey_map/src/lib.rs index dbf2b6f..7d5d214 100644 --- a/2022/day22-monkey_map/src/main.rs +++ b/2022/day22-monkey_map/src/lib.rs @@ -1,4 +1,19 @@ -use std::fs; +use core::fmt::Display; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError<'a> { + InputMalformed(&'a str), + InvalidChar(char), +} + +impl Display for ParseError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InputMalformed(v) => write!(f, "Unable to split Input into Map and Instructions. Input should consist of 2 parts, separated by an empty line:\n{v}"), + Self::InvalidChar(c) => write!(f, "Invalid Character {c} encountered"), + } + } +} #[derive(Clone, Copy, Debug, PartialEq)] enum Direction { @@ -14,7 +29,6 @@ enum Wrapping { Flat, Cube(isize) } #[derive(PartialEq)] enum Walkability { Free, Obstructed, Void } -//#[derive(Debug)] enum Instruction { Go(usize), Turn(char) } #[derive(Clone, Copy)] @@ -193,23 +207,23 @@ impl Position { } } -fn parse_map(string: &str) -> Vec> { +fn try_parse_map(string: &str) -> Result>, ParseError> { string.lines() .map(|line| line.chars() .map(|c| match c { - ' ' => Walkability::Void, - '.' => Walkability::Free, - '#' => Walkability::Obstructed, - _ => panic!("Unexpected Map Item: {c}"), + ' ' => Ok(Walkability::Void), + '.' => Ok(Walkability::Free), + '#' => Ok(Walkability::Obstructed), + _ => Err(ParseError::InvalidChar(c)), }) .collect()) - .collect() + .collect::, _>>() } -fn parse_instructions(line: &str) -> Vec { +fn try_parse_instructions(line: &str) -> Result, ParseError> { let mut instructions = Vec::new(); let mut distance = 0_usize; - line.chars().for_each(|c| { + for c in line.chars() { if let Some(d) = c.to_digit(10) { distance *= 10; distance += d as usize; @@ -219,20 +233,22 @@ fn parse_instructions(line: &str) -> Vec { distance = 0; } instructions.push(Instruction::Turn(c)); + } else if c == '\n' { + continue; + } else { + return Err(ParseError::InvalidChar(c)); } - }); + } if distance > 0 { instructions.push(Instruction::Go(distance)); } - instructions + Ok(instructions) } -fn read_file(path: &str) -> (Vec>, Vec) { - let components = fs::read_to_string(path) - .expect("File not Found"); - let (map_str, instructions_str) = components.split_once("\n\n").unwrap(); - (parse_map(map_str), parse_instructions(instructions_str)) -} +// fn read_file(input: &str) -> (Vec>, Vec) { +// let (map_str, instructions_str) = components.split_once("\n\n").unwrap(); +// (parse_map(map_str), parse_instructions(instructions_str)) +// } fn get_password(map: &[Vec], instructions: &[Instruction], wrapping: Wrapping) -> usize { let mut position = Position { @@ -250,29 +266,38 @@ fn get_password(map: &[Vec], instructions: &[Instruction], wrapping (position.coordinate.row + 1) * 1000 + (position.coordinate.col + 1) * 4 + position.facing as usize } -fn main() { - let (map, instructions) = read_file("input"); - - println!("Flat Map ended up at Password {}", get_password(&map, &instructions, Wrapping::Flat)); +pub fn run(input: &str) -> Result<(usize, usize), ParseError> { + let (map, instructions) = input.split_once("\n\n").ok_or(ParseError::InputMalformed(input))?; + let map = try_parse_map(map)?; + let instructions = try_parse_instructions(instructions)?; + let first = get_password(&map, &instructions, Wrapping::Flat); let side_length = (map.iter().map(|i| i.iter().filter(|&w| *w != Walkability::Void).count()).sum::() as f64 / 6.0).sqrt() as isize; - println!("Cube Map ended up at Password {}.", get_password(&map, &instructions, Wrapping::Cube(side_length))); + let second = get_password(&map, &instructions, Wrapping::Cube(side_length)); + Ok((first, second)) } -#[test] -fn sample_input() { - let (map, instructions) = read_file("tests/sample_input"); +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; - assert_eq!(get_password(&map, &instructions, Wrapping::Flat), 6032); - let side_length = (map.iter().map(|i| i.iter().filter(|&w| *w != Walkability::Void).count()).sum::() as f64 / 6.0).sqrt() as isize; - // Part 2 does not work for the sample input, sice it is shaped differently. - assert_eq!(side_length, 4); -} - -#[test] -fn challenge_input() { - let (map, instructions) = read_file("tests/input"); - - assert_eq!(get_password(&map, &instructions, Wrapping::Flat), 58248); - let side_length = (map.iter().map(|i| i.iter().filter(|&w| *w != Walkability::Void).count()).sum::() as f64 / 6.0).sqrt() as isize; - assert_eq!(get_password(&map, &instructions, Wrapping::Cube(side_length)), 179091); + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]) + } + + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + let (map, instructions) = &sample_input[..].split_once("\n\n").unwrap(); + let map = try_parse_map(map).unwrap(); + let instructions = try_parse_instructions(instructions).unwrap(); + assert_eq!(get_password(&map, &instructions, Wrapping::Flat), 6032); + assert_eq!((map.iter().map(|i| i.iter().filter(|&w| *w != Walkability::Void).count()).sum::() as f64 / 6.0).sqrt() as isize, 4); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((58248, 179091))); + } } diff --git a/2022/day22-monkey_map/tests/input b/2022/day22-monkey_map/tests/challenge_input similarity index 100% rename from 2022/day22-monkey_map/tests/input rename to 2022/day22-monkey_map/tests/challenge_input diff --git a/2022/day23-unstable_diffusion/src/main.rs b/2022/day23-unstable_diffusion/src/lib.rs similarity index 75% rename from 2022/day23-unstable_diffusion/src/main.rs rename to 2022/day23-unstable_diffusion/src/lib.rs index fc0e757..a674e7d 100644 --- a/2022/day23-unstable_diffusion/src/main.rs +++ b/2022/day23-unstable_diffusion/src/lib.rs @@ -1,4 +1,18 @@ -use std::{fs, isize, collections::HashMap}; +use core::fmt::Display; +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + UnexpectedMapFeature(char, usize, usize), +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnexpectedMapFeature(c, x, y) => write!(f, "Trying to parse unexpected map feature {c} at x={x}, y={y}"), + } + } +} #[derive(Clone, Copy, PartialEq)] enum Tile { Free, Elf, ProposedOnce, ProposedMultiple } @@ -105,22 +119,6 @@ impl Elf { } } -fn read_file(path: &str) -> HashMap<(isize, isize), Tile> { - fs::read_to_string(path) - .expect("File not Found") - .lines() - .enumerate() - .flat_map(|(y, l)| l.chars() - .enumerate() - .map(move |(x, c)| ((x as isize, y as isize), match c { - '.' => Tile::Free, - '#' => Tile::Elf, - _ => panic!("Unexpected Map Feature: {c} at {x}, {y}"), - }))) - .collect() -} - - fn get_free_tiles(elfs: &mut [Elf], grid: &mut HashMap<(isize, isize), Tile>, rounds: usize) -> usize { for round in 0..rounds { elfs.iter_mut().for_each(|elf| *elf = elf.consider(round, grid)); @@ -142,7 +140,7 @@ fn get_free_tiles(elfs: &mut [Elf], grid: &mut HashMap<(isize, isize), Tile>, ro fn get_last_round(elfs: &mut [Elf], grid: &mut HashMap<(isize, isize), Tile>, starting_round: usize) -> usize { for round in starting_round.. { elfs.iter_mut().for_each(|elf| *elf = elf.consider(round, grid)); - if !elfs.iter().any(|elf| elf.considered != None) { + if !elfs.iter().any(|elf| elf.considered.is_some()) { return round + 1; } elfs.iter_mut().for_each(|elf| *elf = elf.reposition(grid)); @@ -150,40 +148,45 @@ fn get_last_round(elfs: &mut [Elf], grid: &mut HashMap<(isize, isize), Tile>, st unreachable!("The loop always returns"); } -fn main() { - let mut grid = read_file("input"); - +pub fn run(input: &str) -> Result<(usize, usize), ParseError> { + let mut grid: HashMap<_, _> = input.lines() + .enumerate() + .flat_map(|(y, l)| l.chars() + .enumerate() + .map(move |(x, c)| match c { + '.' => Ok(((x as isize, y as isize), Tile::Free)), + '#' => Ok(((x as isize, y as isize), Tile::Elf)), + _ => Err(ParseError::UnexpectedMapFeature(c, x, y)), + })) + .collect::, _>>()?; let mut elfs: Vec = grid.iter() .filter(|((_, _), &tile)| tile==Tile::Elf) .map(|((x, y), _)| Elf { x: *x, y: *y, considered: None }) .collect(); - println!("After 10 Rounds, {} tiles are free.", get_free_tiles(&mut elfs, &mut grid, 10)); - println!("No more movement after round {}.", get_last_round(&mut elfs, &mut grid, 10)); + let first = get_free_tiles(&mut elfs, &mut grid, 10); + let second = get_last_round(&mut elfs, &mut grid, 10); + Ok((first, second)) } -#[test] -fn sample_input() { - let mut grid = read_file("tests/sample_input"); +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; - let mut elfs: Vec = grid.iter() - .filter(|((_, _), &tile)| tile==Tile::Elf) - .map(|((x, y), _)| Elf { x: *x, y: *y, considered: None }) - .collect(); + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string() + } - assert_eq!(get_free_tiles(&mut elfs, &mut grid, 10), 110); - assert_eq!(get_last_round(&mut elfs, &mut grid, 10), 20); -} - -#[test] -fn challenge_input() { - let mut grid = read_file("tests/input"); - - let mut elfs: Vec = grid.iter() - .filter(|((_, _), &tile)| tile==Tile::Elf) - .map(|((x, y), _)| Elf { x: *x, y: *y, considered: None }) - .collect(); - - assert_eq!(get_free_tiles(&mut elfs, &mut grid, 10), 4068); - assert_eq!(get_last_round(&mut elfs, &mut grid, 10), 968); + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + assert_eq!(run(&sample_input), Ok((110, 20))); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((4068, 968))); + } } diff --git a/2022/day23-unstable_diffusion/tests/input b/2022/day23-unstable_diffusion/tests/challenge_input similarity index 100% rename from 2022/day23-unstable_diffusion/tests/input rename to 2022/day23-unstable_diffusion/tests/challenge_input diff --git a/2022/day24-blizzard_basin/src/main.rs b/2022/day24-blizzard_basin/src/lib.rs similarity index 69% rename from 2022/day24-blizzard_basin/src/main.rs rename to 2022/day24-blizzard_basin/src/lib.rs index bfba6f6..9af6464 100644 --- a/2022/day24-blizzard_basin/src/main.rs +++ b/2022/day24-blizzard_basin/src/lib.rs @@ -1,4 +1,19 @@ -use std::fs; +use core::fmt::Display; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + StartNotFound, + UnexpectedChar(char) +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::StartNotFound => write!(f, "Unable to find a starting blizzard in row 0"), + Self::UnexpectedChar(c) => write!(f, "Trying to parse unexpected character {c} into map"), + } + } +} #[derive(PartialEq)] enum Direction { Up, Down, Left, Right } @@ -65,16 +80,12 @@ impl Blizard { } } -fn read_file(path: &str) -> (Vec>, Vec) { +fn try_parse(input: &str) -> Result<(Vec>, Vec), ParseError> { let mut map = Vec::new(); let mut blizzards = Vec::new(); - fs::read_to_string(path) - .expect("File not Found") - .lines() - .enumerate() - .for_each(|(y, line)| { + for (y, line) in input.lines().enumerate() { let mut this_line = Vec::new(); - line.chars().enumerate().for_each(|(x, c)| { + for (x, c) in line.chars().enumerate() { match &c { '.' => this_line.push(Tile::Blizards(0)), '#' => this_line.push(Tile::Wall), @@ -94,12 +105,12 @@ fn read_file(path: &str) -> (Vec>, Vec) { this_line.push(Tile::Blizards(1)); blizzards.push(Blizard { x, y, direction: Direction::Down }); }, - _ => panic!("Unexpected Map Character: {c}"), + c => return Err(ParseError::UnexpectedChar(*c)), } - }); + } map.push(this_line); - }); - (map, blizzards) + } + Ok((map, blizzards)) } fn get_neighbours((x, y): (usize, usize), max_x: usize, max_y: usize) -> Vec<(usize, usize)> { @@ -149,37 +160,36 @@ fn get_rounds_from_to(start: (usize, usize), destination: (usize, usize), map: & rounds } -fn main() { - let (mut map, mut blizzards) = read_file("input"); - let start = (map[0].iter().position(|tile| *tile == Tile::Blizards(0)).unwrap(), 0); +pub fn run(input: &str) -> Result<(usize, usize), ParseError> { + let (mut map, mut blizzards) = try_parse(input)?; + let start = (map[0].iter().position(|tile| *tile == Tile::Blizards(0)).ok_or(ParseError::StartNotFound)?, 0); let destination = (map[map.len()-1].iter().position(|tile| *tile == Tile::Blizards(0)).unwrap(), map.len()-1); - let mut rounds = get_rounds_from_to(start, destination, &mut map, &mut blizzards); - println!("Reached desitnation after {} rounds.", rounds); - rounds += get_rounds_from_to(destination, start, &mut map, &mut blizzards); - println!("Reached start again after {} rounds.", rounds); - rounds += get_rounds_from_to(start, destination, &mut map, &mut blizzards); - println!("Reached desitnation after {} rounds.", rounds); + let mut second = get_rounds_from_to(start, destination, &mut map, &mut blizzards); + let first = second; + second += get_rounds_from_to(destination, start, &mut map, &mut blizzards); + second += get_rounds_from_to(start, destination, &mut map, &mut blizzards); + Ok((first, second)) } -#[test] -fn sample_input() { - let (mut map, mut blizzards) = read_file("tests/sample_input"); - let start = (map[0].iter().position(|tile| *tile == Tile::Blizards(0)).unwrap(), 0); - let destination = (map[map.len()-1].iter().position(|tile| *tile == Tile::Blizards(0)).unwrap(), map.len()-1); +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; - assert_eq!(get_rounds_from_to(start, destination, &mut map, &mut blizzards), 18); - assert_eq!(get_rounds_from_to(destination, start, &mut map, &mut blizzards), 23); - assert_eq!(get_rounds_from_to(start, destination, &mut map, &mut blizzards), 13); -} - -#[test] -fn challenge_input() { - let (mut map, mut blizzards) = read_file("tests/input"); - let start = (map[0].iter().position(|tile| *tile == Tile::Blizards(0)).unwrap(), 0); - let destination = (map[map.len()-1].iter().position(|tile| *tile == Tile::Blizards(0)).unwrap(), map.len()-1); - - assert_eq!(get_rounds_from_to(start, destination, &mut map, &mut blizzards), 277); - assert_eq!(get_rounds_from_to(destination, start, &mut map, &mut blizzards), 305); - assert_eq!(get_rounds_from_to(start, destination, &mut map, &mut blizzards), 295); + 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((18, 54))); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((277, 877))); + } } diff --git a/2022/day24-blizzard_basin/tests/input b/2022/day24-blizzard_basin/tests/challenge_input similarity index 100% rename from 2022/day24-blizzard_basin/tests/input rename to 2022/day24-blizzard_basin/tests/challenge_input diff --git a/2022/day25-full_of_hot_air/src/lib.rs b/2022/day25-full_of_hot_air/src/lib.rs new file mode 100644 index 0000000..12d003f --- /dev/null +++ b/2022/day25-full_of_hot_air/src/lib.rs @@ -0,0 +1,126 @@ +use core::fmt::Display; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + InvalidChar(char), +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidChar(c) => write!(f, "Unexpected character: {c} should not be part of a SNAFU number."), + } + } +} + +#[derive(Clone, Copy)] +enum SnafuDigit { Zero, One, Two, Minus, DoubleMinus } +struct Snafu(Vec); + +impl TryFrom<&str> for Snafu { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + Ok(Self(value.chars().map(|c| match c { + '0' => Ok(SnafuDigit::Zero), + '1' => Ok(SnafuDigit::One), + '2' => Ok(SnafuDigit::Two), + '-' => Ok(SnafuDigit::Minus), + '=' => Ok(SnafuDigit::DoubleMinus), + c => Err(Self::Error::InvalidChar(c)), + }).collect::, _>>()?)) + } +} + +impl From for char { + fn from(value: SnafuDigit) -> Self { + match value { + SnafuDigit::Zero => '0', + SnafuDigit::One => '1', + SnafuDigit::Two => '2', + SnafuDigit::Minus => '-', + SnafuDigit::DoubleMinus => '=', + } + } +} + +impl Display for Snafu { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.iter().cloned().map(char::from).collect::()) + } +} + +impl From for isize { + fn from(value: Snafu) -> Self { + let mut res = 0; + + for d in value.0 { + res *= 5; + match d { + SnafuDigit::Zero => (), + SnafuDigit::One => res += 1, + SnafuDigit::Two => res += 2, + SnafuDigit::Minus => res -= 1, + SnafuDigit::DoubleMinus => res -= 2, + } + } + + res + } +} + +impl From for Snafu { + fn from(value: isize) -> Self { + let mut digits = Vec::new(); + let mut value = value; + + while value != 0 { + let digit = value % 5; + match digit { + 0 => digits.push(SnafuDigit::Zero), + 1 => digits.push(SnafuDigit::One), + 2 => digits.push(SnafuDigit::Two), + 3 => digits.push(SnafuDigit::DoubleMinus), + 4 => digits.push(SnafuDigit::Minus), + _ => unreachable!("value%5 can only ever be one of the values above"), + } + if digit > 2 { + value += 2; + } + value /= 5; + } + digits.reverse(); + + Self(digits) + } +} + +pub fn run(input: &str) -> Result { + let total = input.lines() + .map(|s| Snafu::try_from(s).map(isize::from)) + .sum::>()?; + + Ok(format!("{}", Snafu::from(total))) +} + +#[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("2=-1=0".to_string())); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok("2-0=11=-0-2-1==1=-22".to_string())); + } +} diff --git a/2022/day25-full_of_hot_air/src/main.rs b/2022/day25-full_of_hot_air/src/main.rs deleted file mode 100644 index fd0602a..0000000 --- a/2022/day25-full_of_hot_air/src/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::fs; - -fn from_snafu(snafu_num: &str) -> isize { - let mut number = 0; - - snafu_num.chars().for_each(|c| { - number *= 5; - match &c { - '0' | '1' | '2' => number += (c as u8 - b'0') as isize, - '-' => number -= 1, - '=' => number -= 2, - _ => panic!("Unexpected character: {c} should not be part of a SNAFU number."), - } - }); - number -} - -fn to_snafu(number: isize) -> String { - let mut snafu_num = String::new(); - - let mut temp = number; - while temp != 0 { - let digit = ( temp % 5) as u8; - match digit { - 0 | 1 | 2 => snafu_num.push((digit + b'0') as char), - 3 => { - snafu_num.push('='); - temp += 2; - }, - _ => { - snafu_num.push('-'); - temp += 2; - }, - } - temp /= 5; - } - - snafu_num.chars().rev().collect() -} - -fn read_file(path: &str) -> Vec { - fs::read_to_string(path) - .expect("File not Found") - .lines() - .map(String::from) - .collect() -} - -fn main() { - let list = read_file("input"); - - let total = list.iter() - .map(|snafu_num| from_snafu(snafu_num)) - .sum::(); - - println!("The total Fuel Usage is {total}, which is {} in SNAFU numbers.", to_snafu(total)); -} - -#[test] -fn sample_input() { - let list = read_file("tests/sample_input"); - let total = list.iter().map(|s| from_snafu(s)).sum::(); - - assert_eq!(total, 4890); - assert_eq!(to_snafu(total), "2=-1=0"); -} - -#[test] -fn challenge_input() { - let list = read_file("tests/input"); - let total = list.iter().map(|s| from_snafu(s)).sum::(); - - assert_eq!(total, 34061028947237); - assert_eq!(to_snafu(total), "2-0=11=-0-2-1==1=-22"); -} diff --git a/2022/day25-full_of_hot_air/tests/input b/2022/day25-full_of_hot_air/tests/challenge_input similarity index 100% rename from 2022/day25-full_of_hot_air/tests/input rename to 2022/day25-full_of_hot_air/tests/challenge_input