Cleanup for 2022 days 11 through 21: Turned into a lib and introduced parse errors
This commit is contained in:
parent
c7852c9791
commit
fcb2fed515
26 changed files with 1095 additions and 879 deletions
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
// }
|
208
2022/day14-regolith_reservoir/src/lib.rs
Normal file
208
2022/day14-regolith_reservoir/src/lib.rs
Normal 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);
|
||||
// }
|
|
@ -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);
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
123
2022/day18-boiling_boulders/src/lib.rs
Normal file
123
2022/day18-boiling_boulders/src/lib.rs
Normal 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)));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
232
2022/day19-not_enough_minerals/src/lib.rs
Normal file
232
2022/day19-not_enough_minerals/src/lib.rs
Normal 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)));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
BIN
2022/day21-monkey_math/day_monkey_math-4ba.core
Normal file
BIN
2022/day21-monkey_math/day_monkey_math-4ba.core
Normal file
Binary file not shown.
153
2022/day21-monkey_math/src/lib.rs
Normal file
153
2022/day21-monkey_math/src/lib.rs
Normal 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)));
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue