Compare commits
5 Commits
8345fa1c24
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f9930694ec | |||
| 9c2793324a | |||
| 5424772424 | |||
| c14277a2f3 | |||
| 25c4b6617b |
19
.run/run-day-16.run.xml
Normal file
19
.run/run-day-16.run.xml
Normal 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
1
input/day17.txt
Normal file
File diff suppressed because one or more lines are too long
1
input/day17_example.txt
Normal file
1
input/day17_example.txt
Normal file
@@ -0,0 +1 @@
|
||||
>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>
|
||||
2881
input/day18.txt
Normal file
2881
input/day18.txt
Normal file
File diff suppressed because it is too large
Load Diff
13
input/day18_example.txt
Normal file
13
input/day18_example.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
2,2,2
|
||||
1,2,2
|
||||
3,2,2
|
||||
2,1,2
|
||||
2,3,2
|
||||
2,2,1
|
||||
2,2,3
|
||||
2,2,4
|
||||
2,2,6
|
||||
1,2,5
|
||||
3,2,5
|
||||
2,1,5
|
||||
2,3,5
|
||||
282
src/day16.rs
282
src/day16.rs
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::day16::ValveStep::{MoveTo, OpenValve};
|
||||
use crate::day_solver::DaySolver;
|
||||
|
||||
use super::util;
|
||||
@@ -5,65 +7,204 @@ 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()
|
||||
.map(|s| s.split_whitespace().skip(1).next().unwrap().to_string())
|
||||
.map(|s| s.split_whitespace().nth(1).unwrap().to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// Put the input into the day struct
|
||||
return Day16 {
|
||||
valves: lines.iter().enumerate().map(|(i, s)| {
|
||||
let mut s_split = s.split(";");
|
||||
let flow_rate = s_split.next().unwrap().split("rate=").skip(1).next().unwrap().parse::<u32>().unwrap();
|
||||
let connections = (&s_split.next().unwrap()["tunnels lead to valves ".len()..].trim()).split(", ")
|
||||
valves: lines.iter().enumerate().map(|(_, s)| {
|
||||
let mut s_split = s.split(';');
|
||||
let flow_rate = s_split.next().unwrap().split("rate=").nth(1).unwrap().parse::<u32>().unwrap();
|
||||
let connections = s_split.next().unwrap()["tunnels lead to valves ".len()..].trim().split(", ")
|
||||
.map(|n| valve_names.iter().position(|nn| n.eq(nn)).unwrap())
|
||||
.collect();
|
||||
Valve {
|
||||
id: i, flow_rate, connections
|
||||
// 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 is_open(valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
||||
open_valves_mask & (1u64 << valve_idx) != 0
|
||||
}
|
||||
|
||||
fn is_closed(valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
||||
!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 {
|
||||
fn is_open(valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
||||
open_valves_mask & (1u64 << valve_idx) != 0
|
||||
}
|
||||
|
||||
if minutes_left == 0 { return 0; }
|
||||
// fn is_closed(valve_idx: &usize, open_valves_mask: &u64) -> bool {
|
||||
// !Self::is_open(valve_idx, open_valves_mask)
|
||||
// }
|
||||
//
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
// res
|
||||
// }
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
return cur_flow_rate + next_step_options.iter().max().unwrap();
|
||||
|
||||
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(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(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,17 +212,90 @@ 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);
|
||||
|
||||
best.total_flow.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Valve {
|
||||
id: usize,
|
||||
// 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),
|
||||
}
|
||||
148
src/day17.rs
Normal file
148
src/day17.rs
Normal 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, ".")
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/day18.rs
Normal file
205
src/day18.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
use std::collections::{HashSet};
|
||||
use crate::day18::VoxelState::{Air, Unknown};
|
||||
|
||||
use crate::day_solver::DaySolver;
|
||||
|
||||
use super::util;
|
||||
|
||||
|
||||
pub struct Day18 {
|
||||
voxels: HashSet<Vec3>
|
||||
}
|
||||
|
||||
impl Day18 {
|
||||
|
||||
pub fn create() -> Self {
|
||||
// let lines = util::read_file("input/day18_example.txt");
|
||||
let lines = util::read_file("input/day18.txt");
|
||||
|
||||
let voxels = lines.iter().map(|l| {
|
||||
let mut c_iter = l.split(",").map(|c| c.parse().unwrap());
|
||||
Vec3 {
|
||||
x: c_iter.next().unwrap(),
|
||||
y: c_iter.next().unwrap(),
|
||||
z: c_iter.next().unwrap()
|
||||
}
|
||||
}).collect::<HashSet<Vec3>>();
|
||||
|
||||
// Put the input into the day struct
|
||||
return Day18 { voxels }
|
||||
}
|
||||
}
|
||||
|
||||
impl DaySolver for Day18 {
|
||||
|
||||
|
||||
fn solve_part1(&mut self) -> String {
|
||||
|
||||
let mut sides = 0;
|
||||
for voxel in &self.voxels {
|
||||
// We just scan all sides if there are voxels there
|
||||
if !self.voxels.contains(&voxel.moved(-1, 0, 0)) { sides += 1 };
|
||||
if !self.voxels.contains(&voxel.moved(1, 0, 0)) { sides += 1 };
|
||||
if !self.voxels.contains(&voxel.moved(0, -1, 0)) { sides += 1 };
|
||||
if !self.voxels.contains(&voxel.moved(0, 1, 0)) { sides += 1 };
|
||||
if !self.voxels.contains(&voxel.moved(0, 0, -1)) { sides += 1 };
|
||||
if !self.voxels.contains(&voxel.moved(0, 0, 1)) { sides += 1 };
|
||||
}
|
||||
return sides.to_string();
|
||||
}
|
||||
|
||||
fn solve_part2(&mut self) -> String {
|
||||
|
||||
// We need to find the cubes that can't be reached
|
||||
let x_min = self.voxels.iter().map(|v| v.x).min().unwrap();
|
||||
let x_max = self.voxels.iter().map(|v| v.x).max().unwrap();
|
||||
let y_min = self.voxels.iter().map(|v| v.y).min().unwrap();
|
||||
let y_max = self.voxels.iter().map(|v| v.y).max().unwrap();
|
||||
let z_min = self.voxels.iter().map(|v| v.z).min().unwrap();
|
||||
let z_max = self.voxels.iter().map(|v| v.z).max().unwrap();
|
||||
|
||||
let width = (x_max + 1 - x_min) as usize;
|
||||
let height = (y_max + 1 - y_min) as usize;
|
||||
let depth = (z_max + 1 - z_min) as usize;
|
||||
|
||||
|
||||
let mut grid = Grid3D {
|
||||
data: vec![Unknown; width * depth * height],
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
offset: Vec3 { x: x_min, y: y_min, z: z_min },
|
||||
max: Vec3{ x: x_max, y: y_max, z: z_max}
|
||||
};
|
||||
|
||||
for vox in &self.voxels {
|
||||
grid.set(&vox.x, &vox.y, &vox.z, VoxelState::Rock);
|
||||
}
|
||||
|
||||
let mut queue = Vec::new();
|
||||
// Now we mark all voxels on the edge as air (if they're not rock)
|
||||
for x in grid.offset.x..grid.max.x+1 {
|
||||
for y in grid.offset.y..grid.max.y+1 {
|
||||
queue_if_unknown(&mut grid, &mut queue, &x, &y, &z_min);
|
||||
queue_if_unknown(&mut grid, &mut queue, &x, &y, &z_max);
|
||||
}
|
||||
for z in grid.offset.z..grid.max.z+1 {
|
||||
queue_if_unknown(&mut grid, &mut queue, &x, &y_min, &z);
|
||||
queue_if_unknown(&mut grid, &mut queue, &x, &y_max, &z);
|
||||
}
|
||||
}
|
||||
for z in z_min..z_max + 1 {
|
||||
for y in y_min..y_max + 1 {
|
||||
queue_if_unknown(&mut grid, &mut queue, &x_min, &y, &z);
|
||||
queue_if_unknown(&mut grid, &mut queue, &x_max, &y, &z);
|
||||
}
|
||||
}
|
||||
|
||||
// We search one extra cube around the actual grid, so that the search doesn't get blocked
|
||||
// by rocks hitting the outer edge:
|
||||
queue.push(grid.offset.moved(-1, 0, 0));
|
||||
while let Some(pos) = queue.pop() {
|
||||
// If we got here, it means that "pos" is in air, and we need to check if the neighbors are as well
|
||||
try_pos(&mut grid, &mut queue, &pos, 1, 0, 0);
|
||||
try_pos(&mut grid, &mut queue, &pos, -1, 0, 0);
|
||||
try_pos(&mut grid, &mut queue, &pos, 0, 1, 0);
|
||||
try_pos(&mut grid, &mut queue, &pos, 0, -1, 0);
|
||||
try_pos(&mut grid, &mut queue, &pos, 0, 0, 1);
|
||||
try_pos(&mut grid, &mut queue, &pos, 0, 0, -1);
|
||||
}
|
||||
|
||||
// and finally, we do the same check (basically) as in pt 1:
|
||||
let mut sides = 0;
|
||||
for voxel in &self.voxels {
|
||||
// We just scan all sides if there are voxels there
|
||||
if grid.get(&(voxel.x + 1), &voxel.y, &voxel.z) == &Air { sides += 1 };
|
||||
if grid.get(&(voxel.x - 1), &voxel.y, &voxel.z) == &Air { sides += 1 };
|
||||
if grid.get(&voxel.x, &(voxel.y + 1), &voxel.z) == &Air { sides += 1 };
|
||||
if grid.get(&voxel.x, &(voxel.y - 1), &voxel.z) == &Air { sides += 1 };
|
||||
if grid.get(&voxel.x, &voxel.y, &(voxel.z + 1)) == &Air { sides += 1 };
|
||||
if grid.get(&voxel.x, &voxel.y, &(voxel.z - 1)) == &Air { sides += 1 };
|
||||
}
|
||||
// println!("{:?}", grid.data);
|
||||
// return grid.data.iter().filter(|v| v == &&Unknown).count().to_string();
|
||||
sides.to_string()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn try_pos(grid: &mut Grid3D, queue: &mut Vec<Vec3>, pos: &Vec3, offset_x: i32, offset_y: i32, offset_z: i32) {
|
||||
let x = pos.x + offset_x;
|
||||
let y = pos.y + offset_y;
|
||||
let z = pos.z + offset_z;
|
||||
queue_if_unknown(grid, queue, &x, &y, &z);
|
||||
}
|
||||
|
||||
fn queue_if_unknown(grid: &mut Grid3D, queue: &mut Vec<Vec3>, x: &i32, y: &i32, z: &i32) {
|
||||
if grid.get(x, y, z) == &Unknown {
|
||||
grid.set(x, y, z, Air);
|
||||
queue.push(Vec3::new(*x, *y, *z))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
struct Vec3 {
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32
|
||||
}
|
||||
|
||||
impl Vec3 {
|
||||
fn moved(&self, x: i32, y: i32, z: i32) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x + x,
|
||||
y: self.y + y,
|
||||
z: self.z + z
|
||||
}
|
||||
}
|
||||
|
||||
fn new(x: i32, y: i32, z: i32) -> Vec3 {
|
||||
return Vec3 {
|
||||
x, y, z
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Grid3D {
|
||||
data: Vec<VoxelState>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
depth: usize,
|
||||
offset: Vec3,
|
||||
max: Vec3
|
||||
}
|
||||
|
||||
impl Grid3D {
|
||||
fn set(&mut self, x: &i32, y: &i32, z: &i32, value: VoxelState) {
|
||||
|
||||
if x < &self.offset.x || y < &self.offset.y || z < &self.offset.z {
|
||||
panic!("Invalid position to update the grid: ({}, {}, {})", x, y, z);
|
||||
}
|
||||
|
||||
let coord = self.grid_coord(&x, &y, &z);
|
||||
self.data[coord] = value;
|
||||
}
|
||||
|
||||
fn get(&self, x: &i32, y: &i32, z: &i32) -> &VoxelState {
|
||||
if x < &self.offset.x || y < &self.offset.y || z < &self.offset.z ||
|
||||
x > &self.max.x || y > &self.max.y || z > &self.max.z {
|
||||
&VoxelState::Air
|
||||
} else {
|
||||
&self.data[self.grid_coord(x, y, z)]
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_coord(&self, x: &i32, y: &i32, z: &i32) -> usize {
|
||||
(x - self.offset.x) as usize + ((y - self.offset.y) as usize * self.width) + ((z - self.offset.z) as usize * self.width * self.height)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
enum VoxelState {
|
||||
Air, Rock, Unknown
|
||||
}
|
||||
10
src/main.rs
10
src/main.rs
@@ -1,3 +1,5 @@
|
||||
extern crate core;
|
||||
|
||||
use std::time::Instant;
|
||||
use crate::day1::Day1;
|
||||
use crate::day2::Day2;
|
||||
@@ -15,6 +17,8 @@ use crate::day13::Day13;
|
||||
use crate::day14::Day14;
|
||||
use crate::day15::Day15;
|
||||
use crate::day16::Day16;
|
||||
use crate::day17::Day17;
|
||||
use crate::day18::Day18;
|
||||
use crate::day_solver::DaySolver;
|
||||
|
||||
mod util;
|
||||
@@ -35,8 +39,10 @@ mod day13;
|
||||
mod day14;
|
||||
mod day15;
|
||||
mod day16;
|
||||
mod day17;
|
||||
mod day18;
|
||||
|
||||
const MAX_DAY: u8 = 15;
|
||||
const MAX_DAY: u8 = 17;
|
||||
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
|
||||
|
||||
fn main() {
|
||||
@@ -116,6 +122,8 @@ 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())),
|
||||
18 => Some(Box::new(Day18::create())),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user