Cleanup for 2022 days 11 through 21: Turned into a lib and introduced parse errors

This commit is contained in:
Burnus 2023-05-15 18:07:16 +02:00
parent c7852c9791
commit fcb2fed515
26 changed files with 1095 additions and 879 deletions

View file

@ -1,3 +1,18 @@
use core::fmt::Display;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
LineMalformed(String),
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
}
}
}
use std::u8;
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
@ -219,15 +234,19 @@ pub fn get_network_to(destination: Position, grid: &[Vec<u8>]) -> Vec<Vec<Positi
/// - max: The Coordinate of the last character in the last line of map. This is used to
/// determine the extent of the map.
///
/// # Panics
/// # Errors
///
/// This panics if map contains lines of different length.
/// Returns a LineMalformed Error whent the map is non-rectangular. The contained String includes
/// the index of the first line that's of a different length then the lines before, as well as its
/// length and that of line 0.
///
/// # Examples
/// ```
/// use day12_hill_climbing_algorithm::*;
/// let map = "Sabqponm\nabcryxxl\naccszExk\nacctuvwj\nabdefghi";
/// let (grid, start, end, max) = parse(map);
/// let parsed = try_parse(map);
/// assert!(parsed.is_ok());
/// let (grid, start, end, max) = parsed.unwrap();
/// assert_eq!(grid, vec![
/// vec![0, 0, 1, 16, 15, 14, 13, 12],
/// vec![0, 1, 2, 17, 24, 23, 23, 11],
@ -238,9 +257,12 @@ pub fn get_network_to(destination: Position, grid: &[Vec<u8>]) -> Vec<Vec<Positi
/// assert_eq!(start, Coordinate::from(0, 0));
/// assert_eq!(end, Coordinate::from(5, 2));
/// assert_eq!(max, Coordinate::from(7, 4));
///
/// let map = "Sabqponm\nabcryxxl\naccszExk\nac\nabdefghi";
/// assert!(try_parse(map).is_err());
/// ```
///
pub fn parse(map: &str) -> (Vec<Vec<u8>>, Coordinate, Coordinate, Coordinate) {
pub fn try_parse(map: &str) -> Result<(Vec<Vec<u8>>, Coordinate, Coordinate, Coordinate), ParseError> {
let mut grid = Vec::new();
let mut start = Coordinate { x: 0, y: 0, };
let mut end = Coordinate { x: 0, y: 0, };
@ -261,11 +283,51 @@ pub fn parse(map: &str) -> (Vec<Vec<u8>>, Coordinate, Coordinate, Coordinate) {
y: grid.len() as u8 - 1,
};
grid.iter().enumerate().for_each(|(idx, row)| {
for (idx, row) in grid.iter().enumerate() {
if row.len() != max.x as usize + 1 {
panic!("Tried to parse a non-rectangular map. Row {idx} has {} characters, but row 0 has {}.", row.len(), max.x + 1);
return Err(ParseError::LineMalformed(format!("Tried to parse a non-rectangular map. Row {idx} has {} characters, but row 0 has {}.", row.len(), max.x + 1)));
}
}
});
(grid, start, end, max)
Ok((grid, start, end, max))
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let (grid, start, end, max) = try_parse(input)?;
let dest_position = Position::from(25, end, max);
let dest_network = get_network_to(dest_position, &grid);
let first = get_length(&dest_network, |position| position.coordinate() == start);
let second = get_length(&dest_network, |position| position.height() == 0);
Ok((first, second))
}
fn get_length<F>(dest_network: &[Vec<Position>], start_condition: F) -> usize where
F: Fn(&Position) -> bool + Copy {
dest_network.iter()
.enumerate()
.find(|(_length, positions)| positions.iter().any(start_condition))
.unwrap()
.0
}
#[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((31, 29)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((425, 418)));
}
}

View file

@ -20,7 +20,7 @@ fn main() {
//let map = read_file("sample_input");
let map = read_file("input");
let (grid, start, end, max) = parse(&map);
let (grid, start, end, max) = try_parse(&map).unwrap();
let end_position = Position::from(25, end, max);
let dest_network = get_network_to(end_position, &grid);
@ -32,36 +32,3 @@ fn main() {
println!("The shortest scenic route is {} steps long.", shortest_scenic);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sample_input_matches_challenge() {
let map = read_file("tests/sample_input");
let (grid, start, end, max) = parse(&map);
let end_position = Position::from(25, end, max);
let dest_network = get_network_to(end_position, &grid);
let start_finish_length = get_length(&dest_network, |position| position.coordinate() == start);
assert_eq!(start_finish_length, 31);
let shortest_scenic = get_length(&dest_network, |position| position.height() == 0);
assert_eq!(shortest_scenic, 29)
}
#[test]
fn challenge_input_matches_solution() {
let map = read_file("tests/input");
let (grid, start, end, max) = parse(&map);
let end_position = Position::from(25, end, max);
let dest_network = get_network_to(end_position, &grid);
let start_finish_length = get_length(&dest_network, |position| position.coordinate() == start);
assert_eq!(start_finish_length, 425);
let shortest_scenic = get_length(&dest_network, |position| position.height() == 0);
assert_eq!(shortest_scenic, 418)
}
}

View file

@ -1,4 +1,18 @@
use std::{fs, cmp::Ordering};
use core::fmt::Display;
use std::cmp::Ordering;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
LineMalformed(&'a str),
}
impl Display for ParseError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum PacketItem<T> {
@ -13,19 +27,23 @@ struct Pair {
right: PacketItem<usize>,
}
impl Pair {
fn from(read_string: &str) -> Self {
let (first, second) = read_string.split_once('\n').expect("Failed to split input pair.");
impl <'a> TryFrom<&'a str> for Pair {
type Error = ParseError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
if let Some((first, second)) = value.split_once('\n') {
let left = parse_packet_item(first);
let right = parse_packet_item(second);
Self {
Ok(Self {
left,
right,
})
} else {
Err(Self::Error::LineMalformed(value))
}
}
}
fn are_correctly_ordered(left: &PacketItem<usize>, right: &PacketItem<usize>) -> Option<bool> {
@ -118,17 +136,6 @@ fn parse_packet_item(string_representation: &str) -> PacketItem<usize> {
PacketItem::List(sub_items)
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn get_pairs(received: &str) -> Vec<Pair> {
received.split("\n\n")
.map(Pair::from)
.collect()
}
fn get_pair_sum(pairs: &[Pair]) -> usize {
pairs.iter()
.enumerate()
@ -157,33 +164,60 @@ fn decode(pairs: &[Pair]) -> usize {
.enumerate()
.filter(|(_, packet)| *packet == divider1 || *packet == divider2)
.map(|(index, _)| index + 1)
.reduce(|a, b| a*b)
.unwrap()
.product::<usize>()
}
fn main() {
let received = read_file("input");
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let pairs = input.split("\n\n").map(Pair::try_from).collect::<Result<Vec<_>, _>>()?;
let first = get_pair_sum(&pairs);
let second = decode(&pairs);
Ok((first, second))
}
let pairs = get_pairs(&received);
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
println!("The sum of the indexes of correctly ordered pairs is {}", get_pair_sum(&pairs));
println!("The decoder key is {}", decode(&pairs));
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn sample_input() {
let received = read_file("tests/sample_input");
let pairs = get_pairs(&received);
assert_eq!(get_pair_sum(&pairs), 13);
assert_eq!(decode(&pairs), 140);
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((13, 140)));
}
#[test]
fn challenge_input() {
let received = read_file("tests/input");
let pairs = get_pairs(&received);
assert_eq!(get_pair_sum(&pairs), 5659);
assert_eq!(decode(&pairs), 22110);
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((5659, 22110)));
}
}
// fn main() {
// let received = read_file("input");
//
// let pairs = get_pairs(&received);
//
// println!("The sum of the indexes of correctly ordered pairs is {}", get_pair_sum(&pairs));
// println!("The decoder key is {}", decode(&pairs));
// }
//
// #[test]
// fn sample_input() {
// let received = read_file("tests/sample_input");
// let pairs = get_pairs(&received);
//
// assert_eq!(get_pair_sum(&pairs), 13);
// assert_eq!(decode(&pairs), 140);
// }
//
// #[test]
// fn challenge_input() {
// let received = read_file("tests/input");
// let pairs = get_pairs(&received);
//
// assert_eq!(get_pair_sum(&pairs), 5659);
// assert_eq!(decode(&pairs), 22110);
// }

View file

@ -0,0 +1,208 @@
use core::fmt::Display;
use std::num::ParseIntError;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
LineMalformed(&'a str),
ParseIntError(std::num::ParseIntError),
}
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::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
}
}
}
use std::collections::HashSet;
#[derive(PartialEq)]
enum Status { Resting, Falling, Blocked }
#[derive(PartialEq)]
enum Mode { EndlessVoid, WithFloor }
#[derive(PartialEq, Eq, Hash, Clone)]
struct Position {
x: usize,
y: usize,
}
impl <'a> TryFrom<&'a str> for Position {
type Error = ParseError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let components = value.split(',').collect::<Vec<_>>().iter().map(|i| i.parse()).collect::<Result<Vec<usize>, _>>()?;
if !components.len() == 2 {
return Err(Self::Error::LineMalformed(value));
}
Ok(Self {
x: components[0],
y: components[1],
})
}
}
struct Sand {
position: Position,
ymax: usize,
}
const ORIGIN: Position = Position {
x: 500,
y: 0,
};
impl Sand {
fn fall(&mut self, cave: &HashSet<Position>, other_sand: &mut HashSet<Position>, mode: &Mode) -> Status {
// return if we fall below all structures
if *mode == Mode::EndlessVoid && self.position.y >= self.ymax {
return Status::Falling;
}
// or we reached the floor.
if *mode == Mode::WithFloor && self.position.y > self.ymax {
other_sand.insert(self.position.clone());
return Status::Resting;
}
// Fall down if possible
if !cave.contains(&Position{ x: self.position.x, y: self.position.y+1 }) && !other_sand.contains(&Position { x: self.position.x, y: self.position.y+1 }) {
self.position.y += 1;
return self.fall(cave, other_sand, mode);
}
// Next try falling left
if !cave.contains(&Position{ x: self.position.x-1, y: self.position.y+1 }) && !other_sand.contains(&Position { x: self.position.x-1, y: self.position.y+1 }) {
self.position.x -= 1;
self.position.y += 1;
return self.fall(cave, other_sand, mode);
}
// Next try falling right
if !cave.contains(&Position{ x: self.position.x+1, y: self.position.y+1 }) && !other_sand.contains(&Position { x: self.position.x+1, y: self.position.y+1 }) {
self.position.x += 1;
self.position.y += 1;
return self.fall(cave, other_sand, mode);
}
// Else we can't fall any more.
other_sand.insert(self.position.clone());
if self.position == ORIGIN {
Status::Blocked
} else {
Status::Resting
}
}
fn spawn(cave: &HashSet<Position>, ymax: usize, mode: &Mode) -> HashSet<Position> {
let mut other_sand = HashSet::new();
loop {
let mut new_unit = Sand {
position: ORIGIN,
ymax,
};
let new_status = new_unit.fall(cave, &mut other_sand, mode);
if new_status != Status::Resting {
break;
}
}
other_sand
}
}
fn positions_of_formation(formation: &str) -> Result<Vec<Position>, ParseError> {
let mut blocked = Vec::new();
let corners = formation.split(" -> ")
.map(Position::try_from)
.collect::<Result<Vec<Position>, _>>()?;
if corners.len() == 1 {
return Ok(corners);
}
for pair in corners.windows(2).collect::<Vec<&[Position]>>() {
let minx = pair[0].x.min(pair[1].x);
let maxx = pair[0].x.max(pair[1].x);
let miny = pair[0].y.min(pair[1].y);
let maxy = pair[0].y.max(pair[1].y);
for x in minx..=maxx {
for y in miny..=maxy {
blocked.push(Position{ x, y });
}
}
}
Ok(blocked)
}
fn get_cave(scan: &str) -> Result<(HashSet<Position>, usize), ParseError> {
let mut cave = HashSet::new();
for line in scan.lines() {
cave.extend(positions_of_formation(line)?.into_iter());
}
let ymax = cave.iter()
.map(|pos| pos.y)
.max()
.unwrap_or_default();
Ok((cave, ymax))
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
// let items: Vec<_> = input.lines().map(::try_from).collect::<Result<Vec<_>, _>>()?;
let (cave, ymax) = get_cave(input)?;
let first = Sand::spawn(&cave, ymax, &Mode::EndlessVoid).len();
let second = Sand::spawn(&cave, ymax, &Mode::WithFloor).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((24, 93)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((979, 29044)));
}
}
// fn main() {
// let scan = read_file("input");
//
// let (cave, ymax) = get_cave(&scan);
//
// let endless_sand = Sand::spawn(&cave, ymax, &Mode::EndlessVoid);
// println!("In Case of an endless void, {} units of sand will come to a rest", endless_sand.len());
//
// let sand_with_floor = Sand::spawn(&cave, ymax, &Mode::WithFloor);
// println!("In Case of a floor, {} units of sand will be spawned", sand_with_floor.len());
// }
//
// #[test]
// fn sample_input() {
// let scan = read_file("tests/sample_input");
// let (cave, ymax) = get_cave(&scan);
// assert_eq!(Sand::spawn(&cave, ymax, &Mode::EndlessVoid).len(), 24);
// assert_eq!(Sand::spawn(&cave, ymax, &Mode::WithFloor).len(), 93);
// }
//
// #[test]
// fn challenge_input() {
// let scan = read_file("tests/input");
// let (cave, ymax) = get_cave(&scan);
// assert_eq!(Sand::spawn(&cave, ymax, &Mode::EndlessVoid).len(), 979);
// assert_eq!(Sand::spawn(&cave, ymax, &Mode::WithFloor).len(), 29044);
// }

View file

@ -1,160 +0,0 @@
use std::{fs, collections::HashSet};
#[derive(PartialEq)]
enum Status { Resting, Falling, Blocked }
#[derive(PartialEq)]
enum Mode { EndlessVoid, WithFloor }
#[derive(PartialEq, Eq, Hash, Clone)]
struct Position {
x: usize,
y: usize,
}
impl Position {
fn from(string: &str) -> Self {
let components = string.split(',').collect::<Vec<_>>().iter().map(|i| i.parse().unwrap()).collect::<Vec<usize>>();
if !components.len() == 2 {
panic!("unable to parse {string} into Position");
}
Self {
x: components[0],
y: components[1],
}
}
}
struct Sand {
position: Position,
ymax: usize,
}
const ORIGIN: Position = Position {
x: 500,
y: 0,
};
impl Sand {
//fn fall(&mut self, cave: &Vec<Position>, other_sand: &mut Vec<Position>, mode: &Mode) -> Status {
fn fall(&mut self, cave: &HashSet<Position>, other_sand: &mut HashSet<Position>, mode: &Mode) -> Status {
// return if we fall below all structures
if *mode == Mode::EndlessVoid && self.position.y >= self.ymax {
return Status::Falling;
}
// or we reached the floor.
if *mode == Mode::WithFloor && self.position.y > self.ymax {
other_sand.insert(self.position.clone());
return Status::Resting;
}
// Fall down if possible
if !cave.contains(&Position{ x: self.position.x, y: self.position.y+1 }) && !other_sand.contains(&Position { x: self.position.x, y: self.position.y+1 }) {
self.position.y += 1;
return self.fall(cave, other_sand, mode);
}
// Next try falling left
if !cave.contains(&Position{ x: self.position.x-1, y: self.position.y+1 }) && !other_sand.contains(&Position { x: self.position.x-1, y: self.position.y+1 }) {
self.position.x -= 1;
self.position.y += 1;
return self.fall(cave, other_sand, mode);
}
// Next try falling right
if !cave.contains(&Position{ x: self.position.x+1, y: self.position.y+1 }) && !other_sand.contains(&Position { x: self.position.x+1, y: self.position.y+1 }) {
self.position.x += 1;
self.position.y += 1;
return self.fall(cave, other_sand, mode);
}
// Else we can't fall any more.
other_sand.insert(self.position.clone());
if self.position == ORIGIN {
Status::Blocked
} else {
Status::Resting
}
}
fn spawn(cave: &HashSet<Position>, ymax: usize, mode: &Mode) -> HashSet<Position> {
let mut other_sand = HashSet::new();
loop {
let mut new_unit = Sand {
position: ORIGIN,
ymax,
};
let new_status = new_unit.fall(cave, &mut other_sand, mode);
if new_status != Status::Resting {
break;
}
}
other_sand
}
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn positions_of_formation(formation: &str) -> Vec<Position> {
let mut blocked = Vec::new();
let corners = formation.split(" -> ")
.map(Position::from)
.collect::<Vec<Position>>();
if corners.len() == 1 {
return corners;
}
for pair in corners.windows(2).collect::<Vec<&[Position]>>() {
let minx = pair[0].x.min(pair[1].x);
let maxx = pair[0].x.max(pair[1].x);
let miny = pair[0].y.min(pair[1].y);
let maxy = pair[0].y.max(pair[1].y);
for x in minx..=maxx {
for y in miny..=maxy {
blocked.push(Position{ x, y });
}
}
}
blocked
}
fn get_cave(scan: &str) -> (HashSet<Position>, usize){
let cave = scan.lines()
.flat_map(|formation| positions_of_formation(formation).iter()
.cloned()
.collect::<HashSet<_>>())
.collect::<HashSet<_>>();
let ymax = cave.iter()
.map(|pos| pos.y)
.max()
.unwrap();
(cave, ymax)
}
fn main() {
let scan = read_file("input");
let (cave, ymax) = get_cave(&scan);
let endless_sand = Sand::spawn(&cave, ymax, &Mode::EndlessVoid);
println!("In Case of an endless void, {} units of sand will come to a rest", endless_sand.len());
let sand_with_floor = Sand::spawn(&cave, ymax, &Mode::WithFloor);
println!("In Case of a floor, {} units of sand will be spawned", sand_with_floor.len());
}
#[test]
fn sample_input() {
let scan = read_file("tests/sample_input");
let (cave, ymax) = get_cave(&scan);
assert_eq!(Sand::spawn(&cave, ymax, &Mode::EndlessVoid).len(), 24);
assert_eq!(Sand::spawn(&cave, ymax, &Mode::WithFloor).len(), 93);
}
#[test]
fn challenge_input() {
let scan = read_file("tests/input");
let (cave, ymax) = get_cave(&scan);
assert_eq!(Sand::spawn(&cave, ymax, &Mode::EndlessVoid).len(), 979);
assert_eq!(Sand::spawn(&cave, ymax, &Mode::WithFloor).len(), 29044);
}

View file

@ -1,6 +1,29 @@
use std::{fs, collections::{HashSet, HashMap}};
use core::fmt::Display;
use std::num::ParseIntError;
use std::collections::{BTreeSet, BTreeMap};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
ParseIntError(std::num::ParseIntError),
LineMalformed(&'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::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Position {
x: isize,
y: isize,
@ -18,11 +41,13 @@ struct Sensor {
beacon_distance: isize,
}
impl Sensor {
fn from(reading: &str) -> Self {
let components = reading.split(' ').collect::<Vec<&str>>();
impl <'a> TryFrom<&'a str> for Sensor {
type Error = ParseError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let components = value.split(' ').collect::<Vec<&str>>();
if components.len() != 10 {
panic!("{components:?} does not have 10 items.");
return Err(Self::Error::LineMalformed(value));
}
let sensor_x_str = &components[2][2..];
@ -30,9 +55,9 @@ impl Sensor {
let beacon_x_str = &components[8][2..];
let beacon_y_str = &components[9][2..];
let sensor_x = sensor_x_str[0..sensor_x_str.len()-1].parse::<isize>().unwrap();
let sensor_y = sensor_y_str[0..sensor_y_str.len()-1].parse::<isize>().unwrap();
let beacon_x = beacon_x_str[0..beacon_x_str.len()-1].parse::<isize>().unwrap();
let sensor_x = sensor_x_str[0..sensor_x_str.len()-1].parse::<isize>()?;
let sensor_y = sensor_y_str[0..sensor_y_str.len()-1].parse::<isize>()?;
let beacon_x = beacon_x_str[0..beacon_x_str.len()-1].parse::<isize>()?;
let beacon_y = beacon_y_str[0..].parse::<isize>().unwrap();
let position = Position {
@ -44,40 +69,42 @@ impl Sensor {
y: beacon_y,
};
Self {
position: position.clone(),
Ok(Self {
position,
beacon_distance: position.distance_to(&beacon_position),
})
}
}
fn beacon_from(reading: &str) -> Position {
impl Sensor {
fn beacon_from(reading: &str) -> Result<Position, ParseError> {
let components = reading.split(' ').collect::<Vec<&str>>();
if components.len() != 10 {
panic!("{components:?} does not have 10 items.");
return Err(ParseError::LineMalformed(reading));
}
let beacon_x_str = &components[8][2..];
let beacon_y_str = &components[9][2..];
let beacon_x = beacon_x_str[0..beacon_x_str.len()-1].parse::<isize>().unwrap();
let beacon_y = beacon_y_str[0..].parse::<isize>().unwrap();
let beacon_x = beacon_x_str[0..beacon_x_str.len()-1].parse::<isize>()?;
let beacon_y = beacon_y_str[0..].parse::<isize>()?;
Position {
Ok(Position {
x: beacon_x,
y: beacon_y,
}
})
}
fn at_row(&self, row: isize) -> HashSet<isize> {
fn at_row(&self, row: isize) -> BTreeSet<isize> {
let slice_depth = self.beacon_distance - (row-self.position.y).abs();
match slice_depth {
nope if nope <= 0 => HashSet::new(),
nope if nope <= 0 => BTreeSet::new(),
_ => (self.position.x-slice_depth..=self.position.x+slice_depth).collect(),
}
}
fn first_non_reachables(&self, unreachables: &mut HashMap<Position, bool>, min: isize, max: isize) {
fn first_non_reachables(&self, unreachables: &mut BTreeMap<Position, bool>, min: isize, max: isize) {
// top right
for i in 0..=self.beacon_distance {
let x = self.position.x+i;
@ -117,12 +144,7 @@ impl Sensor {
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn beacon_free_positions(row: isize, sensors: &[Sensor], beacons: &HashSet<Position>) -> usize {
fn beacon_free_positions(row: isize, sensors: &[Sensor], beacons: &BTreeSet<Position>) -> usize {
sensors.iter()
.map(|s| s.at_row(row))
.reduce(|a, b| a.union(&b).cloned().collect())
@ -139,8 +161,8 @@ fn is_reachable_by(position: &Position, sensors: &[Sensor]) -> bool {
false
}
fn get_non_reachable(sensors: &[Sensor], beacons: &HashSet<Position>, max: isize) -> isize {
let mut first_non_reachables = HashMap::new();
fn get_non_reachable(sensors: &[Sensor], beacons: &BTreeSet<Position>, max: isize) -> isize {
let mut first_non_reachables = BTreeMap::new();
sensors.iter()
.for_each(|s| s.first_non_reachables(&mut first_non_reachables, 0, max));
@ -152,21 +174,29 @@ fn get_non_reachable(sensors: &[Sensor], beacons: &HashSet<Position>, max: isize
}
}
fn main() {
let readings = read_file("input");
pub fn run(input: &str) -> Result<(usize, isize), ParseError> {
let sensors = input.lines().map(Sensor::try_from).collect::<Result<Vec<_>, _>>()?;
let beacons = input.lines().map(Sensor::beacon_from).collect::<Result<BTreeSet<_>, _>>()?;
let sensors = readings.lines().map(Sensor::from).collect::<Vec<_>>();
let beacons = readings.lines().map(Sensor::beacon_from).collect::<HashSet<_>>();
let first = beacon_free_positions(2_000_000, &sensors, &beacons);
let second = get_non_reachable(&sensors, &beacons, 4_000_000);
Ok((first, second))
}
println!("Not in Line 2_000_000: {}", beacon_free_positions(2_000_000, &sensors, &beacons));
println!("Non-Reachable Position found with frequency {}.", get_non_reachable(&sensors, &beacons, 4_000_000));
#[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 sample_input() {
let readings = read_file("tests/sample_input");
let sensors = readings.lines().map(Sensor::from).collect::<Vec<_>>();
let beacons = readings.lines().map(Sensor::beacon_from).collect::<HashSet<_>>();
let sensors = readings.lines().map(Sensor::try_from).collect::<Result<Vec<_>, _>>().unwrap();
let beacons = readings.lines().map(Sensor::beacon_from).collect::<Result<BTreeSet<_>, _>>().unwrap();
assert_eq!(beacon_free_positions(10, &sensors, &beacons), 26);
assert_eq!(get_non_reachable(&sensors, &beacons, 20), 56000011);
@ -174,10 +204,8 @@ fn sample_input() {
#[test]
fn challenge_input() {
let readings = read_file("tests/input");
let sensors = readings.lines().map(Sensor::from).collect::<Vec<_>>();
let beacons = readings.lines().map(Sensor::beacon_from).collect::<HashSet<_>>();
let readings = read_file("tests/challenge_input");
assert_eq!(beacon_free_positions(2_000_000, &sensors, &beacons), 5367037);
assert_eq!(get_non_reachable(&sensors, &beacons, 4_000_000), 11914583249288);
assert_eq!(run(&readings), Ok((5367037, 11914583249288)));
}
}

View file

@ -1,4 +1,28 @@
use std::{fs, usize, collections::HashMap};
use core::fmt::Display;
use std::num::ParseIntError;
use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
ParseIntError(std::num::ParseIntError),
LineMalformed(&'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::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
}
}
}
#[derive(Clone)]
struct Valve {
@ -40,11 +64,6 @@ fn lookup_or_insert<'a>(array: &mut Vec<&'a str>, value: &'a str) -> u8 {
}
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn try_permutations(valves: &[Valve], distances: &HashMap<(u8,u8),u8>, starting_index: u8, time: u8) -> usize {
let closed_valves: Vec<Valve> = valves.iter().filter(|v| !v.open).cloned().collect();
let mut permutations_map: Vec<Vec<Vec<usize>>> = vec![(0..closed_valves.len()).map(|i| vec![closed_valves[i].id as usize]).collect()];
@ -154,71 +173,68 @@ fn try_permutation(valves: &[Valve], distances: &HashMap<(u8, u8), u8>, permutat
let mut released = 0;
for valve_id in permutation {
time_remaining -= *distances.get(&(last_position as u8, *valve_id as u8)).unwrap() as usize + 1;
released += time_remaining * valves[*valve_id].flow_rate as usize;
released += time_remaining * valves[*valve_id].flow_rate;
last_position = *valve_id;
}
released
}
fn init(scan: &str) -> (Vec<Valve>, HashMap<(u8, u8), u8>, u8) {
fn init(scan: &str) -> Result<(Vec<Valve>, HashMap<(u8, u8), u8>, u8), ParseError> {
let mut ids = Vec::new();
let mut all_valves: Vec<Valve> = scan.lines()
.map(|valve_line| {
let components = valve_line.split(' ').collect::<Vec<&str>>();
if components.len() < 10 { panic!("{valve_line} has fewer than 10 components."); }
if components.len() < 10 { return Err(ParseError::LineMalformed(valve_line)); }
let id = lookup_or_insert(&mut ids, components[1]);
let flow_rate_with_semicolon = &components[4][5..];
let flow_rate = flow_rate_with_semicolon[..flow_rate_with_semicolon.len()-1].parse::<usize>().unwrap();
let flow_rate = flow_rate_with_semicolon[..flow_rate_with_semicolon.len()-1].parse::<usize>()?;
let mut connected_valves = Vec::new();
for other_valve_with_comma in components.iter().skip(9).take(components.len()-10) {
connected_valves.push(lookup_or_insert(&mut ids, &other_valve_with_comma[..other_valve_with_comma.len()-1]));
}
connected_valves.push(lookup_or_insert(&mut ids, components[components.len()-1]));
Valve {
Ok(Valve {
id,
flow_rate,
connected_valves,
open: flow_rate == 0,
}
})
.collect();
})
.collect::<Result<Vec<_>, _>>()?;
all_valves.sort_by_key(|v| v.id);
let all_distances = get_all_distances(&all_valves);
(all_valves, all_distances, lookup_or_insert(&mut ids, "AA"))
Ok((all_valves, all_distances, lookup_or_insert(&mut ids, "AA")))
}
fn main() {
//let scan = read_file("sample_input");
let scan = read_file("input");
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
// let items: Vec<_> = input.lines().map(::try_from).collect::<Result<Vec<_>, _>>()?;
let (all_valves, all_distances, starting_index) = init(input)?;
let first = try_permutations(&all_valves, &all_distances, starting_index, 30);
let second = try_permutations_with_elephants(&all_valves, &all_distances, starting_index, 26);
Ok((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
let (all_valves, all_distances, starting_index) = init(&scan);
//let all_distances = get_all_distances(&all_valves);
//let starting_index = lookup_or_insert(&mut ids, "AA");
println!("Working alone, we release {} units.", try_permutations(&all_valves, &all_distances, starting_index, 30));
let with_elephants = try_permutations_with_elephants(&all_valves, &all_distances, starting_index, 26);
println!("Using elephants, we release {with_elephants} units.");
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn sample_input() {
let scan = read_file("tests/sample_input");
let (all_valves, all_distances, starting_index) = init(&scan);
assert_eq!(try_permutations(&all_valves, &all_distances, starting_index, 30), 1651);
assert_eq!(try_permutations_with_elephants(&all_valves, &all_distances, starting_index, 26), 1707);
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((1651, 1707)));
}
#[test]
fn challenge_input() {
let scan = read_file("tests/input");
let (all_valves, all_distances, starting_index) = init(&scan);
assert_eq!(try_permutations(&all_valves, &all_distances, starting_index, 30), 2056);
assert_eq!(try_permutations_with_elephants(&all_valves, &all_distances, starting_index, 26), 2513);
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((2056, 2513)));
}
}

View file

@ -1,4 +1,17 @@
use std::{fs, usize};
use core::fmt::Display;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
InvalidDirection(char),
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidDirection(c) => write!(f, "Trying to parse invalid character {c} into Direction"),
}
}
}
#[derive(PartialEq)]
enum Shape {
@ -9,15 +22,15 @@ enum Shape {
Square,
}
impl Shape {
fn from(number: usize) -> Self {
match number%5 {
impl <T: Into<usize>> From <T> for Shape {
fn from(number: T) -> Self {
match number.into() % 5 {
0 => Shape::Minus,
1 => Shape::Plus,
2 => Shape::L,
3 => Shape::Pipe,
4 => Shape::Square,
_ => panic!("WTF? This can never happen."),
_ => unreachable!("number%5 can only ever be one of the values above"),
}
}
}
@ -25,6 +38,18 @@ impl Shape {
#[derive(PartialEq, Debug)]
enum Direction { Left, Right, Down }
impl TryFrom<char> for Direction {
type Error = ParseError;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'<' => Ok(Direction::Left),
'>' => Ok(Direction::Right),
c => Err(Self::Error::InvalidDirection(c)),
}
}
}
#[derive(PartialEq)]
enum State { Falling, Resting }
@ -292,7 +317,7 @@ impl Block {
self.bl_position.x = match direction {
Direction::Left => self.bl_position.x - 1,
Direction::Right => self.bl_position.x + 1,
_ => panic!("unexpected direction"),
Direction::Down => panic!("Didn't expect to be pushed down"),
};
for position in &old_positions {
arena.free(position);
@ -317,14 +342,10 @@ impl PlayArea {
fn occupy(&mut self, coordinates: &Position) {
self.blocked_tiles[coordinates.y][coordinates.x as usize] = true;
//self.max_y = self.max_y.max(coordinates.y as isize);
}
fn free(&mut self, coordinates: &Position) {
self.blocked_tiles[coordinates.y][coordinates.x as usize] = false;
// if coordinates.y as isize == self.max_y && !self.blocked_tiles[coordinates.y].iter().any(|&b| b) {
// self.max_y -= 1; // self.max_y.saturating_sub(1);
// }
}
fn new() -> Self {
@ -336,20 +357,6 @@ impl PlayArea {
}
}
fn parse_directions(winds: &str) -> Vec<Direction> {
winds.chars()
.filter(|c| ['<', '>'].contains(c))
.map(|c| match c {
'<' => Direction::Left,
'>' => Direction::Right,
_ => panic!("unexpected wind direction: {}", c),
}).collect()
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn solve_with_pattern(target: usize, directions: &[Direction]) -> usize {
let mut results: Vec<(usize, usize, usize)> = Vec::new();
@ -369,9 +376,9 @@ fn solve_with_pattern(target: usize, directions: &[Direction]) -> usize {
let state = (i, direction_index, arena.max_y as usize);
let old_results: Vec<(usize, usize, usize)> = results.iter().filter(|(old_i, old_direction, _)| old_i % 5 == i % 5 && *old_direction == direction_index).cloned().collect();
if old_results.len() > 1 {
let period = (i - old_results[1].0) as usize;
let period = i - old_results[1].0;
let period_growth = arena.max_y as usize - old_results[1].2;
let offset = results[target % period].2 as usize;
let offset = results[target % period].2;
return (target/period) * period_growth + offset;
} else {
results.push(state);
@ -383,30 +390,31 @@ fn solve_with_pattern(target: usize, directions: &[Direction]) -> usize {
0
}
fn main() {
let winds = read_file("input");
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let directions = input.chars().map(Direction::try_from).collect::<Result<Vec<_>, _>>()?;
let first = solve_with_pattern(2022, &directions);
let second = solve_with_pattern(1_000_000_000_000, &directions);
Ok((first, second))
}
let directions = parse_directions(&winds);
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
[2022, 1_000_000_000_000].iter().for_each(|target| {
println!("After {} rocks have fallen, the tower is {} units high.", target, solve_with_pattern(*target, &directions));
});
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn sample_input() {
let winds = read_file("tests/sample_input");
let directions = parse_directions(&winds);
assert_eq!(solve_with_pattern(2022, &directions), 3069);
assert_eq!(solve_with_pattern(1_000_000_000_000, &directions), 1514285714288);
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((3069, 1514285714288)));
}
#[test]
fn challenge_input() {
let winds = read_file("tests/input");
let directions = parse_directions(&winds);
assert_eq!(solve_with_pattern(2022, &directions), 3219);
assert_eq!(solve_with_pattern(1_000_000_000_000, &directions), 1582758620701);
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((3219, 1582758620701)));
}
}

View file

@ -0,0 +1,123 @@
use core::fmt::Display;
use std::{num::ParseIntError, collections::BTreeSet};
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
ParseIntError(std::num::ParseIntError),
LineMalformed(&'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::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Voxel {
x: i8,
y: i8,
z: i8,
}
impl<'a> TryFrom<&'a str> for Voxel {
type Error = ParseError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let components = value.split(',').collect::<Vec<&str>>();
if components.len() != 3 {
Err(Self::Error::LineMalformed(value))
} else {
Ok(Self {
x: components[0].parse()?,
y: components[1].parse()?,
z: components[2].parse()?,
})
}
}
}
impl Voxel {
fn neighbours(&self) -> [Self; 6] {
[
Self { x: self.x-1, y: self.y, z: self.z },
Self { x: self.x+1, y: self.y, z: self.z },
Self { x: self.x, y: self.y-1, z: self.z },
Self { x: self.x, y: self.y+1, z: self.z },
Self { x: self.x, y: self.y, z: self.z-1 },
Self { x: self.x, y: self.y, z: self.z+1 },
]
}
}
fn find_total_surface_area(voxels: &BTreeSet<Voxel>) -> usize {
voxels.iter()
.map(|v| 6 - v.neighbours().iter().filter(|n| voxels.contains(n)).count())
.sum()
}
fn find_area_reachable_from_origin(voxels: &BTreeSet<Voxel>) -> usize {
let max_x = voxels.last().unwrap().x + 1;
let max_y = voxels.iter().map(|v| v.y).max().unwrap() + 1;
let max_z = voxels.iter().map(|v| v.z).max().unwrap() + 1;
let mut water = BTreeSet::from([Voxel { x: 0, y: 0, z: 0 }]);
let mut water_last_step = water.clone();
loop {
let mut water_this_step = BTreeSet::new();
for droplet in &water_last_step {
for neighbour in droplet.neighbours() {
if !water.contains(&neighbour) && !voxels.contains(&neighbour) && (-1..=max_x).contains(&neighbour.x) && (-1..=max_y).contains(&neighbour.y) && (-1..=max_z).contains(&neighbour.z) {
water_this_step.insert(neighbour);
water.insert(neighbour);
}
}
}
if water_this_step.is_empty() {
break;
}
std::mem::swap(&mut water_this_step, &mut water_last_step);
}
voxels.iter()
.map(|v| v.neighbours().iter().filter(|n| water.contains(n)).count())
.sum()
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let voxels: BTreeSet<_> = input.lines().map(Voxel::try_from).collect::<Result<BTreeSet<_>, _>>()?;
let first = find_total_surface_area(&voxels);
let second = find_area_reachable_from_origin(&voxels);
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((64, 58)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((4320, 2456)));
}
}

View file

@ -1,139 +0,0 @@
use std::fs;
#[derive(Clone, PartialEq)]
struct Voxel {
x: i8,
y: i8,
z: i8,
}
impl Voxel {
fn from(line: &str) -> Self {
let components = line.split(',').collect::<Vec<&str>>();
if components.len() != 3 {
panic!("Unable to parse line {} into a Voxel.", line);
}
Self {
x: components[0].parse().unwrap(),
y: components[1].parse().unwrap(),
z: components[2].parse().unwrap(),
}
}
}
fn find_total_surface_area(voxels: &Vec<Voxel>) -> usize {
let mut total_surface = 0;
for voxel in voxels {
total_surface += 6 - voxels.iter()
.filter(|v|
v.x == voxel.x && v.y == voxel.y && (v.z - voxel.z).abs() == 1 ||
v.x == voxel.x && (v.y - voxel.y).abs() == 1 && v.z == voxel.z ||
(v.x - voxel.x).abs() == 1 && v.y == voxel.y && v.z == voxel.z
)
.count()
}
total_surface
}
fn find_area_reachable_from_origin(voxels: &Vec<Voxel>) -> usize {
let max_x = voxels.iter().map(|v| v.x).max().unwrap() + 1;
let max_y = voxels.iter().map(|v| v.y).max().unwrap() + 1;
let max_z = voxels.iter().map(|v| v.z).max().unwrap() + 1;
let mut water = vec![vec![Voxel { x: 0, y: 0, z: 0 }]];
loop {
let mut water_this_step = Vec::new();
for water_from_last_step in &water[water.len()-1] {
let existing_water = water.iter().flatten().cloned().collect::<Vec<Voxel>>();
for x_shift in [-1,1] {
let this_dropplet = Voxel {
x: water_from_last_step.x + x_shift,
y: water_from_last_step.y,
z: water_from_last_step.z,
};
if !existing_water.contains(&this_dropplet) &&
!water_this_step.contains(&this_dropplet) &&
!voxels.contains(&this_dropplet) &&
this_dropplet.x <= max_x && this_dropplet.x >= -1 {
water_this_step.push(this_dropplet);
}
}
for y_shift in [-1,1] {
let this_dropplet = Voxel {
x: water_from_last_step.x,
y: water_from_last_step.y + y_shift,
z: water_from_last_step.z,
};
if !existing_water.contains(&this_dropplet) &&
!water_this_step.contains(&this_dropplet) &&
!voxels.contains(&this_dropplet) &&
this_dropplet.y <= max_y && this_dropplet.y >= -1 {
water_this_step.push(this_dropplet);
}
}
for z_shift in [-1,1] {
let this_dropplet = Voxel {
x: water_from_last_step.x,
y: water_from_last_step.y,
z: water_from_last_step.z + z_shift,
};
if !existing_water.contains(&this_dropplet) &&
!water_this_step.contains(&this_dropplet) &&
!voxels.contains(&this_dropplet) &&
this_dropplet.z <= max_z && this_dropplet.z >= -1 {
water_this_step.push(this_dropplet);
}
}
}
if water_this_step.is_empty() {
break;
}
water.push(water_this_step);
}
let mut total_surface = 0;
for voxel in voxels {
total_surface += water.iter()
.flatten()
.filter(|v|
v.x == voxel.x && v.y == voxel.y && (v.z - voxel.z).abs() == 1 ||
v.x == voxel.x && (v.y - voxel.y).abs() == 1 && v.z == voxel.z ||
(v.x - voxel.x).abs() == 1 && v.y == voxel.y && v.z == voxel.z
)
.count()
}
total_surface
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not found")
}
fn main() {
let scan = read_file("input");
let voxels = scan.lines().map(Voxel::from).collect::<Vec<_>>();
println!("The total surface Area including air pockets is {}.", find_total_surface_area(&voxels));
println!("The outside surface Area is {}.", find_area_reachable_from_origin(&voxels));
}
#[test]
fn sample_input() {
let scan = read_file("tests/sample_input");
let voxels = scan.lines().map(Voxel::from).collect::<Vec<_>>();
assert_eq!(find_total_surface_area(&voxels), 64);
assert_eq!(find_area_reachable_from_origin(&voxels), 58);
}
#[test]
fn challenge_input() {
let scan = read_file("tests/input");
let voxels = scan.lines().map(Voxel::from).collect::<Vec<_>>();
assert_eq!(find_total_surface_area(&voxels), 4320);
assert_eq!(find_area_reachable_from_origin(&voxels), 2456);
}

View file

@ -0,0 +1,232 @@
use core::fmt::Display;
use std::num::ParseIntError;
use std::collections::{HashMap, VecDeque};
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
ParseIntError(std::num::ParseIntError),
LineMalformed(&'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::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
}
}
}
#[derive(Debug)]
struct Blueprint {
id: usize,
ore_robot_cost: u8,
clay_robot_cost: u8,
obsidian_robot_ore_cost: u8,
obsidian_robot_clay_cost: u8,
geode_robot_ore_cost: u8,
geode_robot_obsidian_cost: u8,
}
impl<'a> TryFrom<&'a str> for Blueprint {
type Error = ParseError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let components: Vec<&str> = value.split(' ').collect();
if components.len() != 32 {
return Err(Self::Error::LineMalformed(value));
}
Ok(Self {
id: components[1][..components[1].len()-1].parse()?,
ore_robot_cost: components[6].parse()?,
clay_robot_cost: components[12].parse()?,
obsidian_robot_ore_cost: components[18].parse()?,
obsidian_robot_clay_cost: components[21].parse()?,
geode_robot_ore_cost: components[27].parse()?,
geode_robot_obsidian_cost: components[30].parse()?,
})
}
}
fn more_robots_required(robot_count: u8, stock: u8, max_demand: u8, time_remaining: u8) -> bool {
(robot_count as usize * time_remaining as usize + stock as usize) < (max_demand as usize * time_remaining as usize)
}
impl Blueprint {
fn collect_geodes(&self, time: u8) -> u8 {
self.try_bfs(time)
}
fn try_bfs(&self, time: u8) -> u8 {
let mut open_set = VecDeque::from([(Inventory::new(), time)]);
let mut mem = HashMap::new();
let mut best = 0;
while let Some((inventory, time_remaining)) = open_set.pop_front() {
if time_remaining == 0 {
best = best.max(inventory.geodes);
} else if let Some(best_time) = mem.get(&inventory.as_arr()) {
if *best_time >= time_remaining {
continue;
}
} else {
mem.insert(inventory.as_arr(), time_remaining);
// let inventory = inventory.collect();
// Always buy a Geode Robot if we can afford it and there is at least 1 unit of time remaining
// (so it will produce at least once)
if time_remaining > 1 && inventory.ore >= self.geode_robot_ore_cost && inventory.obsidian >= self.geode_robot_obsidian_cost {
let mut new_inventory = inventory;
new_inventory.collect();
new_inventory.ore -= self.geode_robot_ore_cost;
new_inventory.obsidian -= self.geode_robot_obsidian_cost;
new_inventory.geode_robots += 1;
open_set.push_back((new_inventory, time_remaining-1));
} else {
// Save Ressources only if there is any robot we can't afford, but we already produce the
// required ressource, or we are close to the end
if time_remaining < 4 || inventory.ore < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() ||
inventory.clay < self.obsidian_robot_clay_cost && inventory.clay_robots > 0 ||
inventory.obsidian < self.geode_robot_obsidian_cost && inventory.obsidian_robots > 0
{
let mut new_inventory = inventory;
new_inventory.collect();
open_set.push_back((new_inventory, time_remaining-1));
}
// Buy an Ore Robot if
// - we can afford it, and
// - we don't already produce enough Ore for any other Robot each round, and
// - there are at least 2 rounds left (1 to produce and buy a Geode Robot, 1 for that
// to produce).
// if time_remaining > 2 && inventory.ore >= self.ore_robot_cost && inventory.ore_robots < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() {
if time_remaining > 2 && inventory.ore >= self.ore_robot_cost && more_robots_required(inventory.ore_robots, inventory.ore, *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap(), time_remaining-1) {
let mut new_inventory = inventory;
new_inventory.collect();
new_inventory.ore -= self.ore_robot_cost;
new_inventory.ore_robots += 1;
open_set.push_back((new_inventory, time_remaining-1));
}
// Buy a Clay Robot if
// - we can afford it, and
// - we don't already produce enough Clay for an Obsidian Robot each round, and
// - there are at least 3 rounds left (1 to produce and buy an Obsidian Robot, 1 for that
// to produce and buy a Geode Robot, and 1 for that to produce).
// if time_remaining > 3 && inventory.ore >= self.clay_robot_cost && inventory.clay_robots < self.obsidian_robot_clay_cost {
if time_remaining > 3 && inventory.ore >= self.clay_robot_cost && more_robots_required(inventory.clay_robots, inventory.clay, self.obsidian_robot_clay_cost, time_remaining-1) {
let mut new_inventory = inventory;
new_inventory.collect();
new_inventory.ore -= self.clay_robot_cost;
new_inventory.clay_robots += 1;
open_set.push_back((new_inventory, time_remaining-1));
}
// Buy an Obsidian Robot if
// - we can afford it, and
// - we don't already produce enough Obsidian for a Geode Robot each round, and
// - there are at least 2 rounds left (1 to produce and buy a Geode Robot, and 1 for that
// to produce).
// if time_remaining > 2 && inventory.ore >= self.obsidian_robot_ore_cost && inventory.clay >= self.obsidian_robot_clay_cost && inventory.obsidian_robots < self.geode_robot_obsidian_cost {
if time_remaining > 2 && inventory.ore >= self.obsidian_robot_ore_cost && inventory.clay >= self.obsidian_robot_clay_cost && more_robots_required(inventory.obsidian_robots, inventory.obsidian, self.geode_robot_obsidian_cost, time_remaining-1) {
let mut new_inventory = inventory;
new_inventory.collect();
new_inventory.ore -= self.obsidian_robot_ore_cost;
new_inventory.clay -= self.obsidian_robot_clay_cost;
new_inventory.obsidian_robots += 1;
open_set.push_back((new_inventory, time_remaining-1));
}
}
}
}
best
}
}
#[derive(Copy, Clone, Debug)]
struct Inventory {
ore: u8,
clay: u8,
obsidian: u8,
geodes: u8,
ore_robots: u8,
clay_robots: u8,
obsidian_robots: u8,
geode_robots: u8,
}
impl Inventory {
fn new() -> Self {
Self {
ore: 0,
clay: 0,
obsidian: 0,
geodes: 0,
ore_robots: 1,
clay_robots: 0,
obsidian_robots: 0,
geode_robots: 0,
}
}
fn collect(&mut self) {
self.ore += self.ore_robots;
self.clay += self.clay_robots;
self.obsidian += self.obsidian_robots;
self.geodes += self.geode_robots;
}
fn as_arr(&self) -> [u8;8] {
[
self.ore,
self.clay,
self.obsidian,
self.geodes,
self.ore_robots,
self.clay_robots,
self.obsidian_robots,
self.geode_robots,
]
}
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let blueprints: Vec<Blueprint> = input.lines().map(Blueprint::try_from).collect::<Result<Vec<_>, _>>()?;
let first = blueprints.iter()
.map(|blueprint| blueprint.id * blueprint.collect_geodes(24) as usize)
.sum();
// let second = 0;
let second = blueprints.iter()
.take(3)
.map(|blueprint| blueprint.collect_geodes(32) as usize)
.product();
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((33, 3472)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((978, 15939)));
}
}

View file

@ -1,177 +0,0 @@
use std::{fs, collections::HashMap};
#[derive(Debug)]
struct Blueprint {
id: usize,
ore_robot_cost: u8,
clay_robot_cost: u8,
obsidian_robot_ore_cost: u8,
obsidian_robot_clay_cost: u8,
geode_robot_ore_cost: u8,
geode_robot_obsidian_cost: u8,
}
impl Blueprint {
fn parse(line: &str) -> Self {
let components: Vec<&str> = line.split(' ').collect();
if components.len() != 32 {
panic!("{line} does not have 32 components.");
}
Self {
id: components[1][..components[1].len()-1].parse().unwrap(),
ore_robot_cost: components[6].parse().unwrap(),
clay_robot_cost: components[12].parse().unwrap(),
obsidian_robot_ore_cost: components[18].parse().unwrap(),
obsidian_robot_clay_cost: components[21].parse().unwrap(),
geode_robot_ore_cost: components[27].parse().unwrap(),
geode_robot_obsidian_cost: components[30].parse().unwrap(),
}
}
fn collect_geodes(&self, time: u8) -> u8 {
let best = self.try_all(&Inventory::new(), time, &mut HashMap::new());
println!("Best Result for Blueprint {} is {}", self.id, best);
best
}
fn try_all(&self, inventory: &Inventory, time_remaining: u8, mem: &mut HashMap<[u8;8], u8>) -> u8 {
if let Some(best_time) = mem.get(&inventory.as_arr()) {
if *best_time >= time_remaining && time_remaining > 1 {
return 0;
}
}
mem.insert(inventory.as_arr(), time_remaining);
if time_remaining == 0 {
return inventory.geodes;
}
let mut scores = Vec::new();
// branch
if time_remaining < 4 || inventory.ore < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() ||
inventory.clay < self.obsidian_robot_clay_cost && inventory.clay_robots > 0 ||
inventory.obsidian < self.geode_robot_obsidian_cost && inventory.obsidian_robots > 0
{
let mut new_inventory = *inventory;
new_inventory.collect();
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
}
if time_remaining > 2 && inventory.ore >= self.ore_robot_cost && inventory.ore_robots < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() {
let mut new_inventory = *inventory;
new_inventory.collect();
new_inventory.ore -= self.ore_robot_cost;
new_inventory.ore_robots += 1;
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
}
if time_remaining > 3 && inventory.ore >= self.clay_robot_cost && inventory.clay_robots < self.obsidian_robot_clay_cost {
let mut new_inventory = *inventory;
new_inventory.collect();
new_inventory.ore -= self.clay_robot_cost;
new_inventory.clay_robots += 1;
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
}
if time_remaining > 2 && inventory.ore >= self.obsidian_robot_ore_cost && inventory.clay >= self.obsidian_robot_clay_cost && inventory.obsidian_robots < self.geode_robot_obsidian_cost {
let mut new_inventory = *inventory;
new_inventory.collect();
new_inventory.ore -= self.obsidian_robot_ore_cost;
new_inventory.clay -= self.obsidian_robot_clay_cost;
new_inventory.obsidian_robots += 1;
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
}
if time_remaining > 1 && inventory.ore >= self.geode_robot_ore_cost && inventory.obsidian >= self.geode_robot_obsidian_cost {
let mut new_inventory = *inventory;
new_inventory.collect();
new_inventory.ore -= self.geode_robot_ore_cost;
new_inventory.obsidian -= self.geode_robot_obsidian_cost;
new_inventory.geode_robots += 1;
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
}
scores.iter().cloned().max().unwrap()
}
}
#[derive(Copy, Clone, Debug)]
struct Inventory {
ore: u8,
clay: u8,
obsidian: u8,
geodes: u8,
ore_robots: u8,
clay_robots: u8,
obsidian_robots: u8,
geode_robots: u8,
}
impl Inventory {
fn new() -> Self {
Self {
ore: 0,
clay: 0,
obsidian: 0,
geodes: 0,
ore_robots: 1,
clay_robots: 0,
obsidian_robots: 0,
geode_robots: 0,
}
}
fn collect(&mut self) {
self.ore += self.ore_robots;
self.clay += self.clay_robots;
self.obsidian += self.obsidian_robots;
self.geodes += self.geode_robots;
}
fn as_arr(&self) -> [u8;8] {
[
self.ore,
self.clay,
self.obsidian,
self.geodes,
self.ore_robots,
self.clay_robots,
self.obsidian_robots,
self.geode_robots,
]
}
}
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
fn main() {
let blueprints: Vec<Blueprint> = read_file("input").lines().map(Blueprint::parse).collect();
let quality_level: usize = blueprints.iter()
.map(|blueprint| blueprint.id * blueprint.collect_geodes(24) as usize)
.sum();
println!("The sum of all of our quality levels is {quality_level}"); // should be 33 for the sample_input
let max_score: usize = blueprints.iter()
.take(3)
.map(|blueprint| blueprint.collect_geodes(32) as usize)
.product();
println!("With added time, the remaining blueprints multiply to {max_score}.");
}
#[test]
fn sample_input() {
let blueprints: Vec<_>= read_file("tests/sample_input").lines().map(Blueprint::parse).collect();
let quality_level: usize = blueprints.iter().map(|b| b.id * b.collect_geodes(24) as usize).sum();
let max_score: usize = blueprints.iter().take(3).map(|b| b.collect_geodes(32) as usize).product();
assert_eq!(quality_level, 33);
assert_eq!(max_score, 62);
}
#[test]
fn challenge_input() {
let blueprints: Vec<_>= read_file("tests/input").lines().map(Blueprint::parse).collect();
let quality_level: usize = blueprints.iter().map(|b| b.id * b.collect_geodes(24) as usize).sum();
let max_score: usize = blueprints.iter().take(3).map(|b| b.collect_geodes(32) as usize).product();
assert_eq!(quality_level, 978);
assert_eq!(max_score, 15939);
}

View file

@ -1,14 +1,9 @@
use std::fs;
fn read_file(path: &str) -> String {
fs::read_to_string(path)
.expect("File not Found")
}
use std::num::ParseIntError;
fn get_coordinates(encrypted: &[isize], key: isize, rounds: u8) -> (isize, isize, isize) {
let decrypted = shuffle_with_key(encrypted, key, rounds);
let offset = decrypted.iter().position(|&x| x == 0).unwrap() as usize;
let offset = decrypted.iter().position(|&x| x == 0).unwrap();
let c1 = decrypted[(1000 + offset) % decrypted.len()];
let c2 = decrypted[(2000 + offset) % decrypted.len()];
let c3 = decrypted[(3000 + offset) % decrypted.len()];
@ -43,38 +38,33 @@ fn shuffle_with_key(old: &[isize], key: isize, rounds: u8) -> Vec<isize> {
.collect()
}
fn main() {
let contents = read_file("input");
let encrypted: Vec<isize> = contents.lines().map(|i| i.parse().unwrap()).collect();
pub fn run(input: &str) -> Result<(isize, isize), ParseIntError> {
let encrypted: Vec<isize> = input.lines().map(|i| i.parse()).collect::<Result<Vec<_>, _>>()?;
let (c1, c2, c3) = get_coordinates(&encrypted, 1, 1);
println!("The relevant numbers are {}, {} and {}, totalling {}.", c1, c2, c3, c1+c2+c3);
let (d1, d2, d3) = get_coordinates(&encrypted, 811589153, 10);
println!("With Key, the relevant numbers are {}, {} and {}, totalling {}.", d1, d2, d3, d1+d2+d3);
let first = c1+c2+c3;
let second = d1+d2+d3;
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 sample_input() {
let contents = read_file("tests/sample_input");
let encrypted: Vec<isize> = contents.lines().map(|i| i.parse().unwrap()).collect();
let (c1, c2, c3) = get_coordinates(&encrypted, 1, 1);
let (d1, d2, d3) = get_coordinates(&encrypted, 811589153, 10);
assert_eq!((c1, c2, c3), (4, -3, 2));
assert_eq!((d1, d2, d3), (811589153, 2434767459, -1623178306));
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((3, 1623178306)));
}
#[test]
fn challenge_input() {
let contents = read_file("tests/input");
let encrypted: Vec<isize> = contents.lines().map(|i| i.parse().unwrap()).collect();
let (c1, c2, c3) = get_coordinates(&encrypted, 1, 1);
let (d1, d2, d3) = get_coordinates(&encrypted, 811589153, 10);
assert_eq!((c1, c2, c3), (6790, 9749, -8511));
assert_eq!((d1, d2, d3), (6447264231432, 3708150840057, -1356977063816));
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((8028, 8798438007673)));
}
}

Binary file not shown.

View file

@ -0,0 +1,153 @@
use core::fmt::Display;
use std::num::ParseIntError;
use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
LineMalformed(&'a str),
MissingRoot,
ParseIntError(std::num::ParseIntError),
}
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::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
Self::MissingRoot => write!(f, "Input didn't contain a root monkey"),
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
}
}
}
#[derive(Clone)]
enum Operator { Add, Sub, Mul, Div }
#[derive(Clone)]
struct Operation {
left: String,
operator: Operator,
right: String,
}
impl Operation {
fn from(left: String, operator: String, right: String) -> Self {
Self {
left,
operator: match &operator[..] {
"+" => Operator::Add,
"-" => Operator::Sub,
"*" => Operator::Mul,
"/" => Operator::Div,
_ => panic!("Unknown Operator: {operator}"),
},
right,
}
}
fn perform(&self, monkeys: &HashMap<String, Monkey>) -> f64 {
let left = monkeys.get(&self.left).unwrap().get_number(monkeys);
let right = monkeys.get(&self.right).unwrap().get_number(monkeys);
match self.operator {
Operator::Add => left + right,
Operator::Sub => left - right,
Operator::Mul => left * right,
Operator::Div => left / right,
}
}
}
#[derive(Clone)]
struct Monkey {
number: Option<f64>,
operation: Operation,
}
impl Monkey {
fn get_number(&self, monkeys: &HashMap<String, Monkey>) -> f64 {
if let Some(number) = self.number {
number
} else {
self.operation.perform(monkeys)
}
}
}
fn try_parse(input: &str) -> Result<HashMap<String, Monkey>, ParseError> {
let mut monkeys = HashMap::new();
for line in input.lines() {
let components = line.split(' ').collect::<Vec<&str>>();
let name = components[0][..components[0].len()-1].to_string();
let (number, operation) = match components.len() {
2 => (Some(components[1].parse().unwrap()), Operation { left: name.to_string(), operator: Operator::Add, right: "none".to_string() }),
4 => (None, Operation::from(components[1].to_string(), components[2].to_string(), components[3].to_string())),
_ => return Err(ParseError::LineMalformed(line)),
};
monkeys.insert(name, Monkey { number, operation });
}
Ok(monkeys)
}
fn guess_number(left: Monkey, rigth: Monkey, last_guess: f64, monkeys: &mut HashMap<String, Monkey>) -> isize {
monkeys.insert("humn".to_string(), Monkey { number: Some(last_guess), operation: Operation { left: "none".to_string(), operator: Operator::Add, right: "none".to_string() } });
let diff0 = rigth.get_number(monkeys) - left.get_number(monkeys);
if diff0 == 0.0 {
return last_guess as isize;
}
monkeys.insert("humn".to_string(), Monkey { number: Some(last_guess+1.0), operation: Operation { left: "none".to_string(), operator: Operator::Add, right: "none".to_string() } });
let diff1 = rigth.get_number(monkeys) - left.get_number(monkeys);
if diff1 == 0.0 {
return last_guess as isize + 1;
}
if diff0 == diff1 {
return guess_number(left, rigth, last_guess-1.0, monkeys);
}
let mut next_guess = (last_guess + diff0/(diff0-diff1)).round();
if next_guess == last_guess {
next_guess -= 1.0;
}
guess_number(left, rigth, next_guess, monkeys)
}
pub fn run(input: &str) -> Result<(isize, isize), ParseError> {
let mut monkeys = try_parse(input)?;
let root = monkeys.get(&"root".to_string()).ok_or(ParseError::MissingRoot)?;
let rl = monkeys.get(&root.operation.left).unwrap().clone();
let rr = monkeys.get(&root.operation.right).unwrap().clone();
let first = root.get_number(&monkeys) as isize;
let second = guess_number(rl, rr, 0.0, &mut monkeys);
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((152, 301)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((158661812617812, 3352886133831)));
}
}

View file

@ -1,129 +0,0 @@
use std::{fs, collections::HashMap};
#[derive(Clone)]
enum Operator { Add, Sub, Mul, Div }
#[derive(Clone)]
struct Operation {
left: String,
operator: Operator,
right: String,
}
impl Operation {
fn from(left: String, operator: String, right: String) -> Self {
Self {
left,
operator: match &operator[..] {
"+" => Operator::Add,
"-" => Operator::Sub,
"*" => Operator::Mul,
"/" => Operator::Div,
_ => panic!("Unknown Operator: {operator}"),
},
right,
}
}
fn perform(&self, monkeys: &HashMap<String, Monkey>) -> isize {
let left = monkeys.get(&self.left).unwrap().get_number(monkeys);
let right = monkeys.get(&self.right).unwrap().get_number(monkeys);
match self.operator {
Operator::Add => left + right,
Operator::Sub => left - right,
Operator::Mul => left * right,
Operator::Div => left / right,
}
}
}
#[derive(Clone)]
struct Monkey {
number: Option<isize>,
operation: Operation,
}
impl Monkey {
fn get_number(&self, monkeys: &HashMap<String, Monkey>) -> isize {
if let Some(number) = self.number {
number
} else {
self.operation.perform(monkeys)
}
}
}
fn read_file(path: &str) -> HashMap<String, Monkey> {
let mut monkeys = HashMap::new();
fs::read_to_string(path)
.expect("File not Found")
.lines()
.for_each(|line| {
let components = line.split(' ').collect::<Vec<&str>>();
let name = components[0][..components[0].len()-1].to_string();
let (number, operation) = match components.len() {
2 => (Some(components[1].parse().unwrap()), Operation { left: name.to_string(), operator: Operator::Add, right: "none".to_string() }),
4 => (None, Operation::from(components[1].to_string(), components[2].to_string(), components[3].to_string())),
_ => panic!("Unexpected number of components in {line}"),
};
monkeys.insert(name, Monkey { number, operation });
});
monkeys
}
fn guess_number(left: Monkey, rigth: Monkey, last_guess: isize, monkeys: &mut HashMap<String, Monkey>) -> isize {
monkeys.insert("humn".to_string(), Monkey { number: Some(last_guess), operation: Operation { left: "none".to_string(), operator: Operator::Add, right: "none".to_string() } });
let diff0 = rigth.get_number(monkeys) - left.get_number(monkeys);
if diff0 == 0 {
return last_guess;
}
monkeys.insert("humn".to_string(), Monkey { number: Some(last_guess+1), operation: Operation { left: "none".to_string(), operator: Operator::Add, right: "none".to_string() } });
let diff1 = rigth.get_number(monkeys) - left.get_number(monkeys);
if diff1 == 0 {
return last_guess+1;
}
if diff0 == diff1 {
return guess_number(left, rigth, last_guess-1, monkeys);
}
let mut next_guess = last_guess + diff0/(diff0-diff1);
if next_guess == last_guess {
next_guess -= 1;
}
guess_number(left, rigth, next_guess, monkeys)
}
fn main() {
let mut monkeys = read_file("input");
let root = monkeys.get(&"root".to_string()).unwrap();
println!("The root number is {}", root.get_number(&monkeys));
let rl = monkeys.get(&root.operation.left).unwrap().clone() ;
let rr = monkeys.get(&root.operation.right).unwrap().clone();
println!("You should yell {}", guess_number(rl, rr, 0, &mut monkeys));
}
#[test]
fn sample_input() {
let mut monkeys = read_file("tests/sample_input");
let root = monkeys.get(&"root".to_string()).unwrap();
assert_eq!(root.get_number(&monkeys), 152);
assert_eq!(guess_number(monkeys.get(&root.operation.left).unwrap().clone(), monkeys.get(&root.operation.right).unwrap().clone(), 0, &mut monkeys), 301);
}
#[test]
fn challenge_input() {
let mut monkeys = read_file("tests/input");
let root = monkeys.get(&"root".to_string()).unwrap();
assert_eq!(root.get_number(&monkeys), 158661812617812);
// There are actually multiple solutions for my input. I orginally found 3352886133831 (the smallest of
// them). This algorithm finds 3352886133834 (the largest) if seeded with 0. 3352886133832 is also valid
assert!((3352886133831..=3352886133834).contains(&guess_number(monkeys.get(&root.operation.left).unwrap().clone(), monkeys.get(&root.operation.right).unwrap().clone(), 0, &mut monkeys)));
}