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 = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"num",
|
"num",
|
||||||
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -138,6 +140,12 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.138"
|
version = "0.2.138"
|
||||||
@ -243,6 +251,12 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|||||||
@ -11,6 +11,8 @@ itertools = "^0.10"
|
|||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
env_logger = "^0.10"
|
env_logger = "^0.10"
|
||||||
num = "^0.1"
|
num = "^0.1"
|
||||||
|
lazy_static = "^1.4"
|
||||||
|
once_cell = "^1.16"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "d1p1"
|
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,
|
item,
|
||||||
target
|
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);
|
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 day11;
|
||||||
pub mod day12;
|
pub mod day12;
|
||||||
pub mod day13;
|
pub mod day13;
|
||||||
|
pub mod day14;
|
||||||
|
pub mod day15;
|
||||||
|
pub mod day16;
|
||||||
|
pub mod day17;
|
||||||
pub mod day2;
|
pub mod day2;
|
||||||
pub mod day3;
|
pub mod day3;
|
||||||
pub mod day4;
|
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)
|
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