Compare commits

...

3 Commits

Author SHA1 Message Date
5424772424 [TASK] Solved Day 17 pt 1 2022-12-20 00:23:48 +01:00
c14277a2f3 [TASK] Solved day 16 but it's extremely slow 2022-12-19 17:06:02 +01:00
25c4b6617b [TASK] Day 16 part 1 2022-12-19 15:26:56 +01:00
7 changed files with 422 additions and 26 deletions

19
.run/run-day-16.run.xml Normal file
View File

@@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="run-day-16" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package advent-of-code-2022-rust --bin advent-of-code-2022-rust --release -- -d 16" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

1
input/day17.txt Normal file

File diff suppressed because one or more lines are too long

1
input/day17_example.txt Normal file
View File

@@ -0,0 +1 @@
>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use crate::day16::ValveStep::{MoveTo, OpenValve};
use crate::day_solver::DaySolver;
use super::util;
@@ -5,13 +7,14 @@ use super::util;
pub struct Day16 {
valves: Vec<Valve>,
initial_valve_idx: usize,
valve_names: Vec<String>
}
impl Day16 {
pub fn create() -> Self {
let lines = util::read_file("input/day16_example.txt");
// let lines = util::read_file("input/day16.txt");
// let lines = util::read_file("input/day16_example.txt");
let lines = util::read_file("input/day16.txt");
// We first map all the valve names to indexes for performance reasons:
let valve_names = lines.iter()
@@ -30,10 +33,15 @@ impl Day16 {
id: i, flow_rate, connections
}
}).collect(),
initial_valve_idx: valve_names.iter().position(|n| n == "AA").unwrap()
initial_valve_idx: valve_names.iter().position(|n| n == "AA").unwrap(),
valve_names
}
}
fn set_open(valve_idx: &usize, cur_open_valves_mask: &u64) -> u64 {
cur_open_valves_mask.clone() | (1u64 << valve_idx)
}
fn is_open(valve_idx: &usize, open_valves_mask: &u64) -> bool {
open_valves_mask & (1u64 << valve_idx) != 0
}
@@ -42,28 +50,162 @@ impl Day16 {
!Self::is_open(valve_idx, open_valves_mask)
}
fn set_open(valve_idx: &usize, cur_open_valves_mask: &u64) -> u64 {
cur_open_valves_mask.clone() | (1u64 << valve_idx)
}
fn find_best_option(&self, cur_valve_idx: &usize, open_valves_mask: u64, cur_flow_rate: u32, minutes_left: u32) -> u32 {
if minutes_left == 0 { return 0; }
let cur_valve = &self.valves.get(*cur_valve_idx).unwrap();
let mut next_step_options = Vec::new();
if Self::is_closed(&cur_valve_idx, &open_valves_mask) && cur_valve.flow_rate > 0 {
// One option is to open the valve at the current position
next_step_options.push(
self.find_best_option(cur_valve_idx, Self::set_open(&cur_valve_idx, &open_valves_mask), cur_flow_rate + cur_valve.flow_rate, minutes_left - 1)
);
} else {
// We try and move to an adjacent valve, see if we can be more useful there
for connection in &cur_valve.connections {
next_step_options.push(self.find_best_option(connection, open_valves_mask, cur_flow_rate, minutes_left - 1));
fn calc_flow_rate(&self, open_valves_mask: &u64) -> u32 {
let mut res = 0;
for i in 0..self.valves.len() {
if Self::is_open(&i, open_valves_mask) {
res += self.valves[i].flow_rate
}
}
return cur_flow_rate + next_step_options.iter().max().unwrap();
res
}
fn should_valve_be_opened(&self, valve_idx: &usize, open_valves_mask: &u64) -> bool {
if Self::is_open(valve_idx, open_valves_mask) { false }
else if let Some(valve) = self.valves.get(*valve_idx) {
valve.flow_rate > 0
} else { false }
}
fn find_best_option_pt2(&self, valve_state: ValveStatePt2, cur_flow_rate: u32, cache: &mut HashMap<ValveStatePt2, BestOptionPt2>) -> BestOptionPt2 {
if valve_state.minutes_left == 0 {
return BestOptionPt2 {
// my_step: ValveStep::None,
// elephant_step: ValveStep::None,
total_flow: 0,
// cur_flow: cur_flow_rate
}
}
if let Some(res) = cache.get(&valve_state) {
return res.to_owned();
}
let mut my_options = Vec::new();
let mut elephant_options = Vec::new();
if self.should_valve_be_opened(&valve_state.my_position, &valve_state.open_valves_mask) {
// One option is to open the valve at the current position
my_options.push(OpenValve(valve_state.my_position));
}
if valve_state.my_position != valve_state.elephant_position && self.should_valve_be_opened(&valve_state.elephant_position, &valve_state.open_valves_mask) {
elephant_options.push(OpenValve(valve_state.elephant_position));
}
// We try and move to an adjacent valve, see if we can be more useful there
let my_valve = &self.valves[valve_state.my_position];
for connection in my_valve.connections.iter() {
my_options.push(MoveTo(connection.to_owned()))
}
let elephant_valve = &self.valves[valve_state.elephant_position];
for connection in elephant_valve.connections.iter() {
elephant_options.push(MoveTo(connection.to_owned()))
}
// Now we try all combinations of my and elephants options:
let mut best: u32 = 0;
let mut my_best_step = None;
let mut elephant_best_step = None;
let next_minutes_left = valve_state.minutes_left - 1;
for my_option in my_options {
let mut my_next_position = valve_state.my_position;
let mut next_valve_mask_for_me = valve_state.open_valves_mask;
let mut next_flow_rate_for_me = cur_flow_rate;
match my_option {
MoveTo(idx) => my_next_position = idx,
OpenValve(idx) => {
next_valve_mask_for_me = Self::set_open(&idx, &next_valve_mask_for_me);
next_flow_rate_for_me += self.valves[idx].flow_rate
}
_ => {}
}
for elephant_option in &elephant_options {
let mut next_valve_mask = next_valve_mask_for_me;
let mut next_flow_rate = next_flow_rate_for_me;
let mut elephant_next_position = valve_state.elephant_position;
match elephant_option {
MoveTo(idx) => elephant_next_position = *idx,
OpenValve(idx) => {
next_valve_mask = Self::set_open(&idx, &next_valve_mask);
next_flow_rate += self.valves[*idx].flow_rate
}
_ => {}
}
let res = self.find_best_option_pt2(ValveStatePt2::new(my_next_position, elephant_next_position, next_valve_mask, next_minutes_left), next_flow_rate, cache);
if best == 0 || res.total_flow > best {
best = res.total_flow;
my_best_step = Some(my_option);
elephant_best_step = Some(elephant_option.to_owned());
}
}
}
let res = BestOptionPt2 {
// my_step: my_best_step.unwrap(),
// elephant_step: elephant_best_step.unwrap(),
// cur_flow: cur_flow_rate,
total_flow: best + cur_flow_rate
};
cache.insert(valve_state, res.to_owned());
res
}
fn find_best_option(&self, valve_state: ValveState, cur_flow_rate: u32, cache: &mut HashMap<ValveState, BestOptionResult>) -> Option<BestOptionResult> {
if valve_state.minutes_left == 0 { return None }
if let Some(res) = cache.get(&valve_state) {
return Some(res.clone());
}
let cur_valve = &self.valves.get(valve_state.my_position).unwrap();
let mut best_step_option: Option<ValveStep> = None;
let mut best_step_result_option: Option<BestOptionResult> = None;
if self.should_valve_be_opened(&valve_state.my_position, &valve_state.open_valves_mask) {
// One option is to open the valve at the current position
let new_open_valves_mask = Self::set_open(&valve_state.my_position, &valve_state.open_valves_mask);
best_step_result_option = self.find_best_option(ValveState::new(valve_state.my_position, new_open_valves_mask, valve_state.minutes_left - 1), cur_flow_rate + cur_valve.flow_rate, cache);
best_step_option = Some(ValveStep::OpenValve(valve_state.my_position));
}
// We try and move to an adjacent valve, see if we can be more useful there
for connection in cur_valve.connections.iter().rev() {
let next_step_result = self.find_best_option(ValveState::new(connection.to_owned(), valve_state.open_valves_mask, valve_state.minutes_left - 1), cur_flow_rate, cache);
if best_step_result_option.is_none() || next_step_result.as_ref().unwrap().total_flow > best_step_result_option.as_ref().unwrap().total_flow {
best_step_result_option = next_step_result;
best_step_option = Some(ValveStep::MoveTo(connection.to_owned()));
}
}
let Some(best_step) = best_step_option else { panic!("Couldn't find a good step?") };
let mut flow_per_step = Vec::with_capacity(valve_state.minutes_left as usize);
flow_per_step.push(cur_flow_rate);
let mut steps = Vec::with_capacity(valve_state.minutes_left as usize);
steps.push(best_step);
let mut best_next_flow = 0;
if let Some(best_step_result) = best_step_result_option {
flow_per_step.extend(best_step_result.flow_per_step.iter());
steps.extend(best_step_result.steps.iter());
best_next_flow = best_step_result.total_flow;
}
let step_result = BestOptionResult {
total_flow: best_next_flow + cur_flow_rate,
flow_per_step,
steps,
};
cache.insert(valve_state, step_result.clone());
Some(step_result)
}
}
@@ -71,11 +213,36 @@ impl DaySolver for Day16 {
fn solve_part1(&mut self) -> String {
self.find_best_option(&self.initial_valve_idx, 0, 0, 10).to_string()
let mut cache= HashMap::new();
// let example_flows = [0, 0, 0, 20, 20, 20, 33, 33, 33, 33, 54, 54, 54, 54, 54, 54, 54, 54, 76, 76, 76, 76, 79, 79, 79, 81, 81, 81, 81, 81, 81];
// let mut prev_best: u32 = 0;
// let mut example_running_total = 0;
// for i in 1..31u32 {
// let Some(best) = self.find_best_option(ValveState::new(self.initial_valve_idx, 0, i), 0, &mut cache) else { panic!("Not best!")};
// let cur_example_flow = example_flows[i as usize];
// example_running_total += cur_example_flow;
// println!("Best in {} minutes is {}, so I guess the flow is {}, while example flow is {} (example running total {})", i, best.total_flow, best.total_flow - prev_best, cur_example_flow, example_running_total);
// prev_best = best.total_flow;
// }
let best = self.find_best_option(ValveState::new(self.initial_valve_idx, 0, 30), 0, &mut cache).unwrap();
// for i in 0..best.steps.len() {
// println!("Step {}", i + 1);
// match best.steps[i] {
// ValveStep::OpenValve(v) => println!("Opening valve {}", self.valve_names[v]),
// ValveStep::MoveTo(v ) => println!("Moving to valve {}", self.valve_names[v])
// }
// println!("The flow is now {}\n", best.flow_per_step[i]);
// }
best.total_flow.to_string()
}
fn solve_part2(&mut self) -> String {
return 0.to_string();
let mut cache= HashMap::new();
let best = self.find_best_option_pt2(ValveStatePt2::new(self.initial_valve_idx, self.initial_valve_idx, 0, 26), 0, &mut cache);
return best.total_flow.to_string();
}
}
@@ -84,4 +251,53 @@ struct Valve {
id: usize,
flow_rate: u32,
connections: Vec<usize>
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct ValveState {
my_position: usize,
open_valves_mask: u64,
minutes_left: u32
}
impl ValveState {
fn new(cur_valve_idx: usize, open_valves_mask: u64, minutes_left: u32) -> Self{
ValveState { my_position: cur_valve_idx, open_valves_mask, minutes_left }
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct ValveStatePt2 {
my_position: usize,
elephant_position: usize,
open_valves_mask: u64,
minutes_left: u32
}
#[derive(Debug, Clone, Copy)]
struct BestOptionPt2 {
// my_step: ValveStep,
// elephant_step: ValveStep,
total_flow: u32,
// cur_flow: u32
}
impl ValveStatePt2 {
fn new(my_position: usize, elephant_position: usize, open_valves_mask: u64, minutes_left: u32) -> Self{
ValveStatePt2 { my_position, elephant_position, open_valves_mask, minutes_left }
}
}
#[derive(Debug, Clone)]
struct BestOptionResult {
steps: Vec<ValveStep>,
flow_per_step: Vec<u32>,
total_flow: u32
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum ValveStep {
OpenValve(usize),
MoveTo(usize),
None
}

148
src/day17.rs Normal file
View File

@@ -0,0 +1,148 @@
use std::fmt::{Display, Formatter};
use lazy_static::lazy_static;
use crate::day_solver::DaySolver;
use crate::util::{Coord, Grid};
use super::util;
pub struct Day17 {
jet_pattern: Vec<char>
}
impl Day17 {
pub fn create() -> Self {
// let lines = util::read_file("input/day17_example.txt");
let lines = util::read_file("input/day17.txt");
// Put the input into the day struct
return Day17 {
jet_pattern: lines.first().unwrap().chars().collect()
}
}
fn rock_can_occupy_pos(&self, rock: &Grid<bool>, pos: &Coord<usize>, grid: &Grid<CaveCell>) -> bool {
if rock.width + pos.x > grid.width {
return false;
}
for y in 0..rock.height() {
for x in 0..rock.width {
if rock.get(&x, &y) == &true && grid.get(&(x + pos.x), &(pos.y + y)) == &CaveCell::Rock {
return false
}
}
}
return true;
}
}
impl DaySolver for Day17 {
fn solve_part1(&mut self) -> String {
let mut cave = Grid {
data: vec![CaveCell::Empty; 7 * 2022 * 4],
width: 7
};
let mut cur_spawn_height = 3;
let mut jet_iter = self.jet_pattern.iter().cycle();
for i in 0..2022 {
// Spawn the rock
let Some(rock) = ROCK_SHAPES.get(i % ROCK_SHAPES.len()) else { panic!("Can't find rock {}", i % ROCK_SHAPES.len())};
// The rock_pos is the bottom-left corner of the rock
let mut rock_pos = Coord::new(2, cur_spawn_height);
loop {
match jet_iter.next() {
Some('>') => {
let next_pos = Coord::new(rock_pos.x + 1, rock_pos.y);
if self.rock_can_occupy_pos(rock, &next_pos, &cave) {
rock_pos = next_pos;
}
}
Some('<') => {
if rock_pos.x > 0 {
let next_pos = Coord::new(rock_pos.x - 1, rock_pos.y);
if self.rock_can_occupy_pos(rock, &next_pos, &cave) {
rock_pos = next_pos;
}
}
}
m => { panic!("Unknown rock movement: {}...", m.unwrap_or(&'?')); }
}
// Now we move the rock down
if rock_pos.y == 0 {
break;
}
let next_pos = Coord::new(rock_pos.x, rock_pos.y - 1);
if self.rock_can_occupy_pos(rock, &next_pos, &cave) {
rock_pos = next_pos;
} else {
break;
}
}
// place the rock
for y in 0..rock.height() {
for x in 0..rock.width {
if rock.get(&x, &y) == &true {
cave.set(&(rock_pos.x + x), &(rock_pos.y + y), CaveCell::Rock);
}
}
}
cur_spawn_height = cur_spawn_height.max(rock_pos.y + rock.height() + 3);
}
cave.print_range(0, 7, 0, 40);
return (cur_spawn_height-3).to_string();
}
fn solve_part2(&mut self) -> String {
return 0.to_string();
}
}
lazy_static! {
static ref ROCK_SHAPES: Vec<Grid<bool>> = vec![
Grid { data: vec![
true, true, true, true],
width: 4},
Grid { data: vec![
false, true, false,
true, true, true,
false, true, false],
width: 3},
Grid {data: vec![
true, true, true,
false, false, true,
false, false, true,
], width: 3},
Grid { data: vec![
true,
true,
true,
true], width: 1},
Grid { data: vec![
true, true,
true, true],
width: 2 },
];
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum CaveCell {
Rock, Empty
}
impl Display for CaveCell {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CaveCell::Rock => write!(f, "#"),
CaveCell::Empty => write!(f, ".")
}
}
}

View File

@@ -1,3 +1,5 @@
extern crate core;
use std::time::Instant;
use crate::day1::Day1;
use crate::day2::Day2;
@@ -15,6 +17,7 @@ use crate::day13::Day13;
use crate::day14::Day14;
use crate::day15::Day15;
use crate::day16::Day16;
use crate::day17::Day17;
use crate::day_solver::DaySolver;
mod util;
@@ -35,8 +38,9 @@ mod day13;
mod day14;
mod day15;
mod day16;
mod day17;
const MAX_DAY: u8 = 15;
const MAX_DAY: u8 = 17;
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
fn main() {
@@ -116,6 +120,7 @@ fn build_day_solver(day: u8) -> Option<Box<dyn DaySolver>> {
14 => Some(Box::new(Day14::create())),
15 => Some(Box::new(Day15::create())),
16 => Some(Box::new(Day16::create())),
17 => Some(Box::new(Day17::create())),
_ => None
}
}

View File

@@ -84,6 +84,12 @@ pub struct Coord<T> where T: Sized {
pub y: T
}
impl<T> Coord<T> where T: Sized {
pub(crate) fn new(x: T, y: T) -> Self {
Coord { x, y }
}
}
impl <T> Coord<T> where T: Integer + Copy {
pub fn manhattan_dist(&self, other: &Coord<T>) -> T {
self.x.max(other.x).sub(self.x.min(other.x)) + self.y.max(other.y).sub(self.y.min(other.y))