Compare commits
10 Commits
f10e8a4c04
...
22d9f7c42a
| Author | SHA1 | Date | |
|---|---|---|---|
| 22d9f7c42a | |||
| 1e7eb12da9 | |||
| 06786817f3 | |||
| 46d409fa92 | |||
| 0d30968d3e | |||
| 62e9d17eff | |||
| 619c77aab7 | |||
| d05780e64a | |||
| 215e067d33 | |||
| 6d3686c946 |
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -17,8 +17,10 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"num",
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
||||
@ -138,6 +140,12 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
@ -243,6 +251,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
|
||||
@ -11,6 +11,8 @@ itertools = "^0.10"
|
||||
log = "^0.4"
|
||||
env_logger = "^0.10"
|
||||
num = "^0.1"
|
||||
lazy_static = "^1.4"
|
||||
once_cell = "^1.16"
|
||||
|
||||
[[bin]]
|
||||
name = "d1p1"
|
||||
|
||||
17
src/bin/d14p1.rs
Normal file
17
src/bin/d14p1.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use aoc22::{day14, util};
|
||||
|
||||
pub fn main() {
|
||||
let mut cave = day14::parse_cave(&util::parse_input(), false);
|
||||
|
||||
println!("Initial cave:");
|
||||
cave.print();
|
||||
|
||||
let mut sand = 0;
|
||||
while cave.drop_sand() {
|
||||
sand += 1;
|
||||
}
|
||||
|
||||
println!("Final cave:");
|
||||
cave.print();
|
||||
println!("Total sand dropped: {}", sand);
|
||||
}
|
||||
17
src/bin/d14p2.rs
Normal file
17
src/bin/d14p2.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use aoc22::{day14, util};
|
||||
|
||||
pub fn main() {
|
||||
let mut cave = day14::parse_cave(&util::parse_input(), true);
|
||||
|
||||
println!("Initial cave:");
|
||||
cave.print();
|
||||
|
||||
let mut sand = 0;
|
||||
while cave.drop_sand() {
|
||||
sand += 1;
|
||||
}
|
||||
|
||||
println!("Final cave:");
|
||||
cave.print();
|
||||
println!("Total sand dropped: {}", sand);
|
||||
}
|
||||
39
src/bin/d15p1.rs
Normal file
39
src/bin/d15p1.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use aoc22::{day15, util};
|
||||
use itertools::Itertools;
|
||||
|
||||
const ROW: isize = 2000000;
|
||||
|
||||
pub fn main() {
|
||||
let sensors = day15::parse_sensors(&util::parse_input());
|
||||
|
||||
let covered = sensors
|
||||
.iter()
|
||||
.map(|s| s.covered(ROW))
|
||||
.filter(|r| !r.is_empty())
|
||||
.sorted_by_key(|r| *r.start());
|
||||
|
||||
let mut max_x = isize::MIN;
|
||||
let mut total_covered = 0;
|
||||
for range in covered {
|
||||
let (min, max) = range.into_inner();
|
||||
if min > max_x {
|
||||
total_covered += max - min + 1;
|
||||
} else if max > max_x {
|
||||
total_covered += max - max_x;
|
||||
}
|
||||
max_x = max_x.max(max);
|
||||
}
|
||||
|
||||
let n_beacons = sensors
|
||||
.iter()
|
||||
.map(|s| s.loc_b)
|
||||
.filter(|b| b.0 == ROW)
|
||||
.unique()
|
||||
.count() as isize;
|
||||
|
||||
println!(
|
||||
"Impossible positions in row {}: {}",
|
||||
ROW,
|
||||
total_covered - n_beacons
|
||||
);
|
||||
}
|
||||
70
src/bin/d15p2.rs
Normal file
70
src/bin/d15p2.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
use aoc22::{day15, util};
|
||||
use itertools::Itertools;
|
||||
|
||||
const COORD_MAX: isize = 4000000;
|
||||
const NUM_THREADS: usize = 16;
|
||||
|
||||
pub fn main() {
|
||||
let sensors = Arc::new(day15::parse_sensors(&util::parse_input()));
|
||||
|
||||
let mut handles = vec![];
|
||||
let next_row = Arc::new(Mutex::new(0));
|
||||
|
||||
for _ in 0..NUM_THREADS {
|
||||
let sensors = sensors.clone();
|
||||
let next_row = next_row.clone();
|
||||
let handle = thread::spawn(move || find_possible(&sensors, next_row));
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_possible(sensors: &Vec<day15::Sensor>, next_row: Arc<Mutex<isize>>) {
|
||||
loop {
|
||||
let row = {
|
||||
let mut handle = next_row.lock().unwrap();
|
||||
let result = *handle;
|
||||
if result > COORD_MAX {
|
||||
return;
|
||||
}
|
||||
*handle += 1;
|
||||
result
|
||||
};
|
||||
|
||||
let covered = sensors
|
||||
.iter()
|
||||
.map(|s| s.covered(row))
|
||||
.filter(|r| !r.is_empty())
|
||||
.sorted_by_key(|r| *r.start());
|
||||
|
||||
let mut max_x = isize::MIN;
|
||||
for range in covered {
|
||||
let (min, max) = range.into_inner();
|
||||
if min == max_x + 2 {
|
||||
let x = max_x + 1;
|
||||
println!(
|
||||
"x = {}, y = {}, tuning freq = {}",
|
||||
x,
|
||||
row,
|
||||
x * 4000000 + row
|
||||
)
|
||||
} else if max_x != isize::MIN && min > max_x + 2 {
|
||||
panic!(
|
||||
"More than one coordinate free in row {}: {}..{}",
|
||||
row,
|
||||
max_x + 1,
|
||||
min - 1
|
||||
);
|
||||
}
|
||||
max_x = max_x.max(max);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/bin/d16p1.rs
Normal file
73
src/bin/d16p1.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use aoc22::{day16, util};
|
||||
|
||||
pub fn main() {
|
||||
let valves = Arc::new(day16::parse_valves(&util::parse_input()));
|
||||
let dists = Arc::new(day16::calc_dists(&valves));
|
||||
let state = day16::State::new(&valves);
|
||||
|
||||
let possible_states = Arc::new(Mutex::new(Vec::new()));
|
||||
possible_states.lock().unwrap().push(state);
|
||||
|
||||
let lower_bound = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for _ in 0..16 {
|
||||
let s = possible_states.clone();
|
||||
let l = lower_bound.clone();
|
||||
let v = valves.clone();
|
||||
let d = dists.clone();
|
||||
handles.push(thread::spawn(move || check_states(s, l, v, d)));
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
println!(
|
||||
"Most pressure released is {}",
|
||||
lower_bound.load(Ordering::Relaxed)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn check_states(
|
||||
possible_states: Arc<Mutex<Vec<day16::State>>>,
|
||||
lower_bound: Arc<AtomicUsize>,
|
||||
valves: Arc<Vec<day16::Valve>>,
|
||||
dists: Arc<Vec<Vec<usize>>>,
|
||||
) {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
i += 1;
|
||||
let state = { possible_states.lock().unwrap().pop() };
|
||||
if state.is_none() {
|
||||
break;
|
||||
}
|
||||
let state = state.unwrap();
|
||||
if state.finished() {
|
||||
let score = state.lower_bound();
|
||||
dbg!(score);
|
||||
lower_bound.fetch_max(score, Ordering::Relaxed);
|
||||
} else {
|
||||
let x = day16::possible_actions(&state, &valves, &dists);
|
||||
// let x = state.possible_actions(&valves, &dists);
|
||||
// let state_upper = state.upper_bound(&valves, &dists);
|
||||
for action in x {
|
||||
let action_upper = action.upper_bound(&valves, &dists);
|
||||
// assert!(action_upper <= state_upper);
|
||||
if action_upper > lower_bound.load(Ordering::Relaxed) {
|
||||
possible_states.lock().unwrap().push(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dbg!(i);
|
||||
}
|
||||
13
src/bin/d17p1.rs
Normal file
13
src/bin/d17p1.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use aoc22::{day17, util};
|
||||
|
||||
const BLOCKS: usize = 2022;
|
||||
|
||||
pub fn main() {
|
||||
let jets = day17::parse_jets(&util::parse_input());
|
||||
|
||||
println!(
|
||||
"Height after {} blocks: {}",
|
||||
BLOCKS,
|
||||
day17::do_moves(&jets, BLOCKS)
|
||||
);
|
||||
}
|
||||
13
src/bin/d17p2.rs
Normal file
13
src/bin/d17p2.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use aoc22::{day17, util};
|
||||
|
||||
const BLOCKS: usize = 1_000_000_000_000;
|
||||
|
||||
pub fn main() {
|
||||
let jets = day17::parse_jets(&util::parse_input());
|
||||
|
||||
println!(
|
||||
"Height after {} blocks: {}",
|
||||
BLOCKS,
|
||||
day17::do_moves(&jets, BLOCKS)
|
||||
);
|
||||
}
|
||||
@ -175,7 +175,7 @@ pub fn do_round(monkeys: &Vec<RefCell<Monkey>>, do_worry: bool) {
|
||||
item,
|
||||
target
|
||||
);
|
||||
let mut target = &mut monkeys.get(target).unwrap().borrow_mut().items;
|
||||
let target = &mut monkeys.get(target).unwrap().borrow_mut().items;
|
||||
target.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
135
src/day14.rs
Normal file
135
src/day14.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::util::Coord;
|
||||
|
||||
pub const SAND_SOURCE: Coord = (0, 500);
|
||||
|
||||
pub struct Cave {
|
||||
pub rows: usize,
|
||||
pub cols: usize,
|
||||
pub blocked: Vec<Vec<bool>>,
|
||||
pub sand_source: Coord,
|
||||
}
|
||||
|
||||
impl Cave {
|
||||
pub fn print(&self) {
|
||||
for row in 0..self.rows {
|
||||
for col in 0..self.cols {
|
||||
if (row, col) == self.sand_source {
|
||||
print!("+")
|
||||
} else if self.blocked[row][col] {
|
||||
print!("#");
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
print!("\n")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_sand(&mut self) -> bool {
|
||||
let (y, x) = self.sand_source;
|
||||
let mut y = y as isize;
|
||||
let mut x = x as isize;
|
||||
|
||||
while y < self.rows as isize
|
||||
&& x > 0
|
||||
&& x < self.cols as isize
|
||||
&& !self.is_blocked(self.sand_source.0 as isize, self.sand_source.1 as isize)
|
||||
{
|
||||
if !self.is_blocked(y + 1, x) {
|
||||
y += 1;
|
||||
} else if !self.is_blocked(y + 1, x - 1) {
|
||||
y += 1;
|
||||
x -= 1;
|
||||
} else if !self.is_blocked(y + 1, x + 1) {
|
||||
y += 1;
|
||||
x += 1;
|
||||
} else {
|
||||
self.blocked[y as usize][x as usize] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn is_blocked(&self, y: isize, x: isize) -> bool {
|
||||
!(y < 0 || y >= self.rows as isize || x < 0 || x >= self.cols as isize)
|
||||
&& self.blocked[y as usize][x as usize]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_cave(input: &String, do_floor: bool) -> Cave {
|
||||
let mut paths = HashSet::new();
|
||||
|
||||
for line in input.lines() {
|
||||
let mut path = line.split("->").map(|n| parse_coord(n.trim()));
|
||||
let mut last = path.next().unwrap();
|
||||
paths.insert(last);
|
||||
for next in path {
|
||||
let y_min = next.0.min(last.0);
|
||||
let y_max = next.0.max(last.0);
|
||||
let x_min = next.1.min(last.1);
|
||||
let x_max = next.1.max(last.1);
|
||||
let horizontal = y_min == y_max;
|
||||
let vertical = x_min == x_max;
|
||||
|
||||
assert!(horizontal ^ vertical);
|
||||
if horizontal {
|
||||
for x in x_min..=x_max {
|
||||
paths.insert((y_min, x));
|
||||
}
|
||||
} else {
|
||||
for y in y_min..=y_max {
|
||||
paths.insert((y, x_max));
|
||||
}
|
||||
}
|
||||
|
||||
last = next;
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!(paths.len(), 0);
|
||||
|
||||
let (mut y_min, mut y_max) = paths.iter().map(|c| c.0).minmax().into_option().unwrap();
|
||||
let (mut x_min, mut x_max) = paths.iter().map(|c| c.1).minmax().into_option().unwrap();
|
||||
|
||||
// Sand source is always at y = 0, x = 500
|
||||
y_min = y_min.min(SAND_SOURCE.0);
|
||||
x_min = x_min.min(SAND_SOURCE.1);
|
||||
x_max = x_max.max(SAND_SOURCE.1);
|
||||
|
||||
if do_floor {
|
||||
y_max += 2;
|
||||
let dy = y_max - y_min;
|
||||
x_min = x_min.min(SAND_SOURCE.1 - dy - 1);
|
||||
x_max = x_max.max(SAND_SOURCE.1 + dy + 1);
|
||||
for x in x_min..x_max {
|
||||
paths.insert((y_max, x));
|
||||
}
|
||||
}
|
||||
|
||||
let rows = y_max - y_min + 1;
|
||||
let cols = x_max - x_min + 1;
|
||||
|
||||
let mut blocked = vec![vec![false; cols]; rows];
|
||||
|
||||
for coord in paths {
|
||||
blocked[coord.0 - y_min][coord.1 - x_min] = true;
|
||||
}
|
||||
|
||||
Cave {
|
||||
rows,
|
||||
cols,
|
||||
blocked,
|
||||
sand_source: (SAND_SOURCE.0 - y_min, SAND_SOURCE.1 - x_min),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_coord(input: &str) -> Coord {
|
||||
let (x, y) = input.split_once(',').unwrap();
|
||||
(y.parse().unwrap(), x.parse().unwrap())
|
||||
}
|
||||
50
src/day15.rs
Normal file
50
src/day15.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::util::{Coordinate, SignedCoord};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sensor {
|
||||
pub loc_s: SignedCoord,
|
||||
pub loc_b: SignedCoord,
|
||||
pub range: usize,
|
||||
}
|
||||
|
||||
impl Sensor {
|
||||
pub fn covered(&self, y: isize) -> RangeInclusive<isize> {
|
||||
let dy = self.loc_s.0.abs_diff(y);
|
||||
if dy > self.range {
|
||||
1..=0
|
||||
} else {
|
||||
let range_x = (self.range - dy) as isize;
|
||||
(self.loc_s.1 - range_x)..=(self.loc_s.1 + range_x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sensors(input: &String) -> Vec<Sensor> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let re =
|
||||
Regex::new(r"^Sensor at x=(-?\d+), y=(-?\d+): closest beacon is at x=(-?\d+), y=(-?\d+)$")
|
||||
.unwrap();
|
||||
for line in input.lines() {
|
||||
let captures = re.captures(line).unwrap();
|
||||
let loc_s: SignedCoord = (
|
||||
captures.get(2).unwrap().as_str().parse().unwrap(),
|
||||
captures.get(1).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
let loc_b: SignedCoord = (
|
||||
captures.get(4).unwrap().as_str().parse().unwrap(),
|
||||
captures.get(3).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
result.push(Sensor {
|
||||
loc_s,
|
||||
loc_b,
|
||||
range: loc_s.manhattan(&loc_b),
|
||||
});
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
202
src/day16.rs
Normal file
202
src/day16.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
hash::Hash,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
const TOTAL_TIME: usize = 30;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Valve {
|
||||
pub flow_rate: usize,
|
||||
pub tunnels: Vec<usize>,
|
||||
}
|
||||
|
||||
pub fn parse_valves(input: &String) -> Vec<Valve> {
|
||||
let indices: HashMap<String, usize> = input
|
||||
.lines()
|
||||
.map(|l| l.split(' ').nth(1).unwrap())
|
||||
.sorted()
|
||||
.enumerate()
|
||||
.map(|(i, n)| (n.to_owned(), i))
|
||||
.collect();
|
||||
|
||||
let mut valves = HashMap::new();
|
||||
|
||||
let re =
|
||||
Regex::new(r"^Valve (\w+) has flow rate=(\d+); tunnels? leads? to valves? (.*)$").unwrap();
|
||||
for line in input.lines() {
|
||||
let captures = re
|
||||
.captures(line)
|
||||
.expect(&format!("No captures for {}", line));
|
||||
let name = captures.get(1).unwrap().as_str().to_owned();
|
||||
let flow_rate = captures.get(2).unwrap().as_str().parse().unwrap();
|
||||
let tunnels = captures
|
||||
.get(3)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.split(", ")
|
||||
.map(|s| indices[s])
|
||||
.collect();
|
||||
let valve = Valve { flow_rate, tunnels };
|
||||
valves.insert(name, valve);
|
||||
}
|
||||
|
||||
valves
|
||||
.iter()
|
||||
.map(|(name, v)| (indices[name], v))
|
||||
.sorted_by_key(|(i, _)| *i)
|
||||
.map(|(_, v)| v.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn calc_dists(valves: &Vec<Valve>) -> Vec<Vec<usize>> {
|
||||
let n = valves.len();
|
||||
let mut result = vec![vec![usize::MAX; n]; n];
|
||||
|
||||
for i in 0..n {
|
||||
result[i][i] = 0;
|
||||
let mut next = VecDeque::new();
|
||||
next.push_back(i);
|
||||
while let Some(j) = next.pop_front() {
|
||||
let d_next = result[i][j] + 1;
|
||||
for j in &valves[j].tunnels {
|
||||
let d_tunnel = result[i].get_mut(*j).unwrap();
|
||||
if d_next < *d_tunnel {
|
||||
*d_tunnel = d_next;
|
||||
next.push_back(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
pub opened: u64,
|
||||
pub location: usize,
|
||||
pub flow_rate: usize,
|
||||
pub time_remaining: usize,
|
||||
pub pressure_released: usize,
|
||||
|
||||
interesting: HashSet<usize>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(valves: &Vec<Valve>) -> State {
|
||||
let interesting = valves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if v.flow_rate > 0 { Some(i) } else { None })
|
||||
.collect();
|
||||
State {
|
||||
opened: 0,
|
||||
location: 0, // "AA" has to be the first valve alphabetically
|
||||
flow_rate: 0,
|
||||
time_remaining: TOTAL_TIME,
|
||||
pressure_released: 0,
|
||||
interesting,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finished(&self) -> bool {
|
||||
self.time_remaining == 0 || self.interesting.is_empty()
|
||||
}
|
||||
|
||||
pub fn lower_bound(&self) -> usize {
|
||||
self.pressure_released + self.flow_rate * self.time_remaining
|
||||
}
|
||||
|
||||
pub fn upper_bound(&self, valves: &Vec<Valve>, dists: &Vec<Vec<usize>>) -> usize {
|
||||
let mut additional_flow = 0;
|
||||
for i in &self.interesting {
|
||||
let i = i.clone();
|
||||
if (self.opened & (1 << i)) != 0 {
|
||||
continue;
|
||||
}
|
||||
let dist = dists[self.location][i];
|
||||
if dist < self.time_remaining {
|
||||
let flow = valves[i].flow_rate;
|
||||
additional_flow += flow * (self.time_remaining - dist - 1);
|
||||
}
|
||||
}
|
||||
|
||||
self.lower_bound() + additional_flow
|
||||
}
|
||||
|
||||
pub fn possible_actions(&self, valves: &Vec<Valve>, dists: &Vec<Vec<usize>>) -> Vec<State> {
|
||||
assert!(!self.finished());
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
let loc = self.location;
|
||||
|
||||
if (self.opened & (1 << loc)) == 0 && self.interesting.contains(&loc) {
|
||||
let mut open = self.clone();
|
||||
open.opened |= 1 << loc;
|
||||
// First increase pressure released, then the flow rate
|
||||
open.pressure_released += open.flow_rate;
|
||||
open.flow_rate += valves[loc].flow_rate;
|
||||
open.time_remaining -= 1;
|
||||
open.interesting.remove(&loc);
|
||||
result.push(open);
|
||||
}
|
||||
|
||||
for l in &self.interesting {
|
||||
let i = l.clone();
|
||||
let dist = dists[loc][i];
|
||||
if dist == 0 || dist == usize::MAX || self.time_remaining < dist {
|
||||
continue;
|
||||
}
|
||||
let mut moved = self.clone();
|
||||
moved.location = i;
|
||||
moved.pressure_released += moved.flow_rate * dist;
|
||||
moved.time_remaining -= dist;
|
||||
result.push(moved);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for State {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.opened.hash(state);
|
||||
self.location.hash(state);
|
||||
self.time_remaining.hash(state);
|
||||
self.pressure_released.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for State {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.opened == other.opened
|
||||
&& self.location == other.location
|
||||
&& self.time_remaining == other.time_remaining
|
||||
&& self.pressure_released == other.pressure_released
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for State {}
|
||||
|
||||
pub fn possible_actions(state: &State, valves: &Vec<Valve>, dists: &Vec<Vec<usize>>) -> Vec<State> {
|
||||
lazy_static! {
|
||||
static ref CACHE: Mutex<HashMap<State, Vec<State>>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
let actions = { CACHE.lock().unwrap().get(state).cloned() };
|
||||
if let Some(actions) = actions {
|
||||
// assert_eq!(actions, state.possible_actions(valves, dists));
|
||||
actions.clone()
|
||||
} else {
|
||||
let mut cache = CACHE.lock().unwrap();
|
||||
cache.insert(state.clone(), state.possible_actions(valves, dists));
|
||||
cache[state].clone()
|
||||
}
|
||||
}
|
||||
317
src/day17.rs
Normal file
317
src/day17.rs
Normal file
@ -0,0 +1,317 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::util;
|
||||
|
||||
const VENT_WIDTH: usize = 7;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Jet {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Jet {
|
||||
pub fn shift(&self) -> isize {
|
||||
match &self {
|
||||
Jet::Left => 1,
|
||||
Jet::Right => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_jets(input: &String) -> Vec<Jet> {
|
||||
assert_eq!(input.lines().count(), 1);
|
||||
input
|
||||
.chars()
|
||||
.filter_map(|c| match c {
|
||||
'<' => Some(Jet::Left),
|
||||
'>' => Some(Jet::Right),
|
||||
'\n' => None,
|
||||
_ => panic!("Unknown jet {}", c),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BlockKind {
|
||||
Dash = 0,
|
||||
Plus,
|
||||
L,
|
||||
Pipe,
|
||||
Square,
|
||||
}
|
||||
|
||||
impl BlockKind {
|
||||
pub fn from_num(n: usize) -> BlockKind {
|
||||
match n {
|
||||
0 => BlockKind::Dash,
|
||||
1 => BlockKind::Plus,
|
||||
2 => BlockKind::L,
|
||||
3 => BlockKind::Pipe,
|
||||
4 => BlockKind::Square,
|
||||
_ => panic!("Unknown block kind {}", n),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
match self {
|
||||
BlockKind::Dash => 1,
|
||||
BlockKind::Plus => 3,
|
||||
BlockKind::L => 3,
|
||||
BlockKind::Pipe => 4,
|
||||
BlockKind::Square => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
BlockKind::Dash => 4,
|
||||
BlockKind::Plus => 3,
|
||||
BlockKind::L => 3,
|
||||
BlockKind::Pipe => 1,
|
||||
BlockKind::Square => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vent_rows(&self) -> &'static Vec<VentRow> {
|
||||
static ROWS: OnceCell<Vec<Vec<VentRow>>> = OnceCell::new();
|
||||
let rows = ROWS.get_or_init(|| {
|
||||
vec![
|
||||
vec![0b1111], // Dash
|
||||
vec![0b010, 0b111, 0b010], // Plus
|
||||
vec![0b001, 0b001, 0b111], // L
|
||||
vec![0b1; 4], // Pipe
|
||||
vec![0b11, 0b11], // Square
|
||||
]
|
||||
});
|
||||
&rows
|
||||
.get(*self as usize)
|
||||
.unwrap_or_else(|| panic!("Unknown BlockKind: {:?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
const N_BLOCK_TYPES: usize = 5;
|
||||
|
||||
pub struct Block {
|
||||
kind: BlockKind,
|
||||
pos: util::Coord,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn spawn(&self, vent: &mut Vec<VentRow>) {
|
||||
for (i, row) in self.kind.vent_rows().iter().rev().enumerate() {
|
||||
vent[self.pos.0 + i] |= *row << self.shift_dist();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_range(&self) -> std::ops::Range<usize> {
|
||||
let row = self.pos.0;
|
||||
row..(row + self.kind.height())
|
||||
}
|
||||
|
||||
pub fn can_push(&self, vent: &Vec<VentRow>, jet: Jet) -> bool {
|
||||
if (jet == Jet::Left && self.pos.1 == 0)
|
||||
|| (jet == Jet::Right && self.pos.1 + self.kind.width() >= VENT_WIDTH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let shift = self.shift_dist() as isize + jet.shift();
|
||||
for (i, row) in self.row_range().rev().enumerate() {
|
||||
let bitmask = self.kind.vent_rows()[i] << shift;
|
||||
if vent[row] & bitmask != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn can_fall(&self, vent: &Vec<VentRow>) -> bool {
|
||||
if self.pos.0 == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i, row) in self.row_range().rev().enumerate() {
|
||||
let bitmask = self.kind.vent_rows()[i] << self.shift_dist();
|
||||
if vent[row - 1] & bitmask != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn shift_dist(&self) -> usize {
|
||||
VENT_WIDTH - self.pos.1 - self.kind.width()
|
||||
}
|
||||
}
|
||||
|
||||
type VentRow = u8;
|
||||
|
||||
const CYCLE_SIZE: usize = 5;
|
||||
|
||||
pub fn do_moves(jets: &Vec<Jet>, n_blocks: usize) -> usize {
|
||||
let mut block_i = 0;
|
||||
let mut current_block: Option<Block> = None;
|
||||
let mut vent = vec![0; 4];
|
||||
|
||||
// {jet_i: {round: (height, block_i)}}
|
||||
let mut block_cycles: HashMap<usize, HashMap<usize, (usize, usize)>> = HashMap::new();
|
||||
let mut skipped_height: Option<usize> = None;
|
||||
|
||||
let mut round = 0;
|
||||
while block_i <= n_blocks {
|
||||
let jet_i = round % jets.len();
|
||||
|
||||
if current_block.is_none() {
|
||||
if block_i % N_BLOCK_TYPES == 0 {
|
||||
if skipped_height.is_none() {
|
||||
let entry = block_cycles.entry(jet_i).or_insert_with(HashMap::new);
|
||||
entry.insert(round, (vent.len() - empty_rows(&vent), block_i));
|
||||
if let Some((cycle_height, cycle_blocks)) = find_cycle(entry, CYCLE_SIZE) {
|
||||
println!(
|
||||
"Found cycle ({} height per {} blocks) after {} rounds!",
|
||||
cycle_height, cycle_blocks, round
|
||||
);
|
||||
|
||||
let remaining_blocks = n_blocks - block_i;
|
||||
let cycles = remaining_blocks / cycle_blocks;
|
||||
block_i += cycles * cycle_blocks;
|
||||
skipped_height = Some(cycles * cycle_height);
|
||||
|
||||
println!(
|
||||
"Skipping {} blocks, {} height",
|
||||
cycles * cycle_blocks,
|
||||
cycles * cycle_height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
current_block = Some(spawn_block(&mut vent, block_i % N_BLOCK_TYPES));
|
||||
// println!("Vent as block {} begins falling:", block_i);
|
||||
// print_vent(&vent, current_block.as_ref());
|
||||
// println!("");
|
||||
block_i += 1;
|
||||
}
|
||||
let block = current_block.as_mut().unwrap();
|
||||
|
||||
// Push left/right
|
||||
let jet = jets[jet_i];
|
||||
if block.can_push(&vent, jet) {
|
||||
let (_, ref mut col) = block.pos;
|
||||
*col = ((*col as isize) - jet.shift()) as usize;
|
||||
}
|
||||
|
||||
// Fall
|
||||
if block.can_fall(&vent) {
|
||||
let (ref mut row, _) = block.pos;
|
||||
*row -= 1;
|
||||
} else {
|
||||
block.spawn(&mut vent);
|
||||
current_block = None;
|
||||
}
|
||||
round += 1;
|
||||
}
|
||||
|
||||
vent.len() - empty_rows(&vent) + skipped_height.unwrap_or(0)
|
||||
}
|
||||
|
||||
const SPAWN_OFFSET_X: usize = 2;
|
||||
const SPAWN_OFFSET_Y: usize = 3;
|
||||
|
||||
fn spawn_block(vent: &mut Vec<VentRow>, block: usize) -> Block {
|
||||
let block = BlockKind::from_num(block);
|
||||
|
||||
let mut empty_rows = empty_rows(vent);
|
||||
let diff = (block.height() as isize + 3) - (empty_rows as isize);
|
||||
if diff > 0 {
|
||||
vent.append(&mut vec![0; diff as usize]);
|
||||
empty_rows += diff as usize;
|
||||
}
|
||||
|
||||
let row = vent.len() - empty_rows + SPAWN_OFFSET_Y;
|
||||
let col = SPAWN_OFFSET_X;
|
||||
|
||||
assert!(row + block.height() - 1 < vent.len());
|
||||
|
||||
let block = Block {
|
||||
kind: block,
|
||||
pos: (row, col),
|
||||
};
|
||||
|
||||
block
|
||||
}
|
||||
|
||||
fn empty_rows(vent: &Vec<VentRow>) -> usize {
|
||||
vent.iter()
|
||||
.rev()
|
||||
.position(|r| *r != 0)
|
||||
.unwrap_or_else(|| vent.len())
|
||||
}
|
||||
|
||||
pub fn print_vent(vent: &Vec<VentRow>, block: Option<&Block>) {
|
||||
let block_rows = block.map(|b| b.kind.vent_rows());
|
||||
let block_range = block.map(|b| b.row_range());
|
||||
let block_range = block_range.as_ref();
|
||||
for (r, vent_row) in vent.iter().enumerate().rev() {
|
||||
print!("|");
|
||||
let block_collision = if block.is_some() && block_range.unwrap().contains(&r) {
|
||||
block_rows.unwrap()[block_range.unwrap().end - r - 1] << block.unwrap().shift_dist()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
for i in (0..VENT_WIDTH).rev() {
|
||||
let col = 1 << i;
|
||||
let c = if (block_collision & col) != 0 {
|
||||
'@'
|
||||
} else if (vent_row & col) == 0 {
|
||||
'.'
|
||||
} else {
|
||||
'#'
|
||||
};
|
||||
print!("{}", c);
|
||||
}
|
||||
print!("|\n");
|
||||
}
|
||||
println!("+-------+");
|
||||
}
|
||||
|
||||
fn find_cycle(
|
||||
jet_heights: &HashMap<usize, (usize, usize)>,
|
||||
n_cycles: usize,
|
||||
) -> Option<(usize, usize)> {
|
||||
if jet_heights.len() < n_cycles {
|
||||
return None;
|
||||
}
|
||||
|
||||
for (init_round, (init_height, init_blocks)) in jet_heights {
|
||||
'next_loop: for (next_round, (next_height, next_blocks)) in jet_heights {
|
||||
if init_round == next_round || *next_height < *init_height {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cycle = next_round - init_round;
|
||||
let cycle_height = next_height - init_height;
|
||||
let cycle_blocks = next_blocks - init_blocks;
|
||||
for n in 2..=n_cycles {
|
||||
let round = init_round + n * cycle;
|
||||
let entry = jet_heights.get(&round);
|
||||
if let Some((height, blocks)) = entry {
|
||||
let expected_height = init_height + n * cycle_height;
|
||||
let expected_blocks = init_blocks + n * cycle_blocks;
|
||||
if *height != expected_height || *blocks != expected_blocks {
|
||||
continue 'next_loop;
|
||||
}
|
||||
} else {
|
||||
continue 'next_loop;
|
||||
}
|
||||
}
|
||||
|
||||
return Some((cycle_height, cycle_blocks));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@ -3,6 +3,10 @@ pub mod day10;
|
||||
pub mod day11;
|
||||
pub mod day12;
|
||||
pub mod day13;
|
||||
pub mod day14;
|
||||
pub mod day15;
|
||||
pub mod day16;
|
||||
pub mod day17;
|
||||
pub mod day2;
|
||||
pub mod day3;
|
||||
pub mod day4;
|
||||
|
||||
38
src/util.rs
38
src/util.rs
@ -35,3 +35,41 @@ pub fn max_n<T: Ord + Copy>(slice: &[T], n: usize) -> Result<Vec<T>, ()> {
|
||||
|
||||
Ok(max_vals)
|
||||
}
|
||||
|
||||
pub type Coord = (usize, usize);
|
||||
|
||||
pub trait Coordinate {
|
||||
fn manhattan(&self, other: &Self) -> usize;
|
||||
fn to_signed(&self) -> SignedCoord;
|
||||
fn to_unsigned(&self) -> Coord;
|
||||
}
|
||||
|
||||
impl Coordinate for Coord {
|
||||
fn manhattan(&self, other: &Self) -> usize {
|
||||
return self.0.abs_diff(other.0) + self.1.abs_diff(other.1);
|
||||
}
|
||||
|
||||
fn to_signed(&self) -> SignedCoord {
|
||||
(self.0 as isize, self.1 as isize)
|
||||
}
|
||||
|
||||
fn to_unsigned(&self) -> Coord {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
pub type SignedCoord = (isize, isize);
|
||||
|
||||
impl Coordinate for SignedCoord {
|
||||
fn manhattan(&self, other: &Self) -> usize {
|
||||
return self.0.abs_diff(other.0) + self.1.abs_diff(other.1);
|
||||
}
|
||||
|
||||
fn to_signed(&self) -> SignedCoord {
|
||||
*self
|
||||
}
|
||||
|
||||
fn to_unsigned(&self) -> Coord {
|
||||
(self.0 as usize, self.1 as usize)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user