Skip to content

Commit d90b876

Browse files
committed
2018 day 23
1 parent c8310f9 commit d90b876

File tree

4 files changed

+199
-4
lines changed

4 files changed

+199
-4
lines changed

crates/utils/src/geometry.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ macro_rules! vec_impl {
2222
Self{$($f),+}
2323
}
2424

25+
/// Returns a vector with all components set to the provided value.
26+
#[inline]
27+
#[must_use]
28+
pub const fn splat(v: T) -> Self {
29+
Self{$($f: v),+}
30+
}
31+
2532
/// Returns the manhattan distance from the origin.
2633
#[inline]
2734
#[must_use]
@@ -32,16 +39,29 @@ macro_rules! vec_impl {
3239
T::Unsigned::ZERO $(+ self.$f.unsigned_abs())+
3340
}
3441

35-
/// Returns the manhattan distance from the specified point.
42+
/// Returns the manhattan distance to the specified point.
3643
#[inline]
3744
#[must_use]
38-
pub fn manhattan_distance_from(self, rhs: Self) -> T::Unsigned
45+
pub fn manhattan_distance_to(self, rhs: Self) -> T::Unsigned
3946
where
4047
T: Integer
4148
{
4249
T::Unsigned::ZERO $(+ self.$f.abs_diff(rhs.$f))+
4350
}
4451

52+
/// Returns the manhattan distance to the specified axis-aligned bounding box.
53+
#[inline]
54+
#[must_use]
55+
pub fn manhattan_distance_to_aabb(self, min: Self, max: Self) -> T::Unsigned
56+
where
57+
T: Integer
58+
{
59+
T::Unsigned::ZERO $(
60+
+ min.$f.saturating_sub_0(self.$f)
61+
+ self.$f.saturating_sub_0(max.$f)
62+
)+
63+
}
64+
4565
/// Add the provided signed vector, wrapping on overflow.
4666
///
4767
/// Useful for adding a signed direction onto an unsigned position.

crates/utils/src/number.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub trait Integer:
8282
fn trailing_zeros(self) -> u32;
8383
#[must_use]
8484
fn unsigned_abs(self) -> Self::Unsigned;
85+
#[must_use]
86+
fn saturating_sub_0(self, rhs: Self) -> Self::Unsigned;
8587
}
8688

8789
/// Trait implemented by the primitive unsigned integer types.
@@ -94,7 +96,7 @@ pub trait UnsignedInteger: Integer<Unsigned = Self> + From<u8> {
9496
pub trait SignedInteger: Integer<Signed = Self> + Signed {}
9597

9698
macro_rules! number_impl {
97-
(int => $($u:ty: $s:ty ),+) => {
99+
(int => $($u:ident: $s:ident ),+) => {
98100
$(impl Number for $u {
99101
const ZERO: Self = 0;
100102
const ONE: Self = 1;
@@ -144,6 +146,10 @@ macro_rules! number_impl {
144146
fn unsigned_abs(self) -> Self::Unsigned {
145147
self // no-op for unsigned integers
146148
}
149+
#[inline]
150+
fn saturating_sub_0(self, rhs: Self) -> Self::Unsigned {
151+
self.saturating_sub(rhs)
152+
}
147153
})+
148154

149155
$(impl UnsignedInteger for $u {
@@ -206,11 +212,20 @@ macro_rules! number_impl {
206212
fn unsigned_abs(self) -> Self::Unsigned {
207213
self.unsigned_abs()
208214
}
215+
#[inline]
216+
#[expect(clippy::cast_sign_loss)]
217+
fn saturating_sub_0(self, rhs: Self) -> Self::Unsigned {
218+
// Equivalent to `self.saturating_sub(rhs).max(0) as $u`, but avoids overflow for
219+
// e.g. i32::MAX - i32::MIN
220+
let diff = (self as $u).wrapping_sub(rhs as $u);
221+
let mask = (0 as $u).wrapping_sub($u::from(self >= rhs));
222+
diff & mask
223+
}
209224
})+
210225

211226
$(impl SignedInteger for $s {})+
212227
};
213-
(float => $($t:ty),+) => {$(
228+
(float => $($t:ident),+) => {$(
214229
impl Number for $t {
215230
const ZERO: Self = 0.0;
216231
const ONE: Self = 1.0;

crates/year2018/src/day23.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use std::cmp::Ordering;
2+
use std::collections::BinaryHeap;
3+
use utils::geometry::Vec3;
4+
use utils::prelude::*;
5+
6+
/// Finding the point in range of the most nodes.
7+
#[derive(Clone, Debug)]
8+
pub struct Day23 {
9+
bots: Vec<Bot>,
10+
}
11+
12+
#[derive(Clone, Debug)]
13+
struct Bot {
14+
pos: Vec3<i32>,
15+
r: u32,
16+
}
17+
18+
// 2**29 seems to be the max range of the input, and 2**29 * 3 fits within i32 without overflow.
19+
const MAX_POW2: u32 = 29;
20+
const MIN: i32 = -(1 << MAX_POW2);
21+
const MAX: i32 = (1 << MAX_POW2) - 1;
22+
23+
impl Day23 {
24+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
25+
Ok(Self {
26+
bots: parser::number_range(MIN..=MAX)
27+
.repeat_n(b',')
28+
.with_prefix("pos=<")
29+
.with_suffix(">, r=")
30+
.then(parser::u32())
31+
.map(|(pos, r)| Bot { pos: pos.into(), r })
32+
.parse_lines(input)?,
33+
})
34+
}
35+
36+
#[must_use]
37+
pub fn part1(&self) -> usize {
38+
let strongest = self.bots.iter().max_by_key(|b| b.r).unwrap();
39+
40+
self.bots
41+
.iter()
42+
.filter(|&b| b.pos.manhattan_distance_to(strongest.pos) <= strongest.r)
43+
.count()
44+
}
45+
46+
#[must_use]
47+
pub fn part2(&self) -> u32 {
48+
let initial = Subcube::new(Vec3::splat(MIN), Vec3::splat(MAX), &self.bots);
49+
50+
let mut heap = BinaryHeap::with_capacity(2048);
51+
heap.push(initial);
52+
53+
while let Some(s) = heap.pop() {
54+
if s.size == 1 {
55+
return s.dist;
56+
}
57+
heap.extend(s.split(&self.bots));
58+
}
59+
60+
unreachable!()
61+
}
62+
}
63+
64+
#[derive(Clone, Debug, Eq, PartialEq)]
65+
struct Subcube {
66+
min: Vec3<i32>,
67+
max: Vec3<i32>,
68+
bot_count: usize,
69+
dist: u32,
70+
size: u32,
71+
}
72+
73+
impl Subcube {
74+
#[inline]
75+
fn new(min: Vec3<i32>, max: Vec3<i32>, bots: &[Bot]) -> Self {
76+
Self {
77+
min,
78+
max,
79+
bot_count: bots
80+
.iter()
81+
.filter(|&bot| bot.pos.manhattan_distance_to_aabb(min, max) <= bot.r)
82+
.count(),
83+
dist: Vec3::ORIGIN.manhattan_distance_to_aabb(min, max),
84+
size: max.x.abs_diff(min.x) + 1,
85+
}
86+
}
87+
88+
#[inline]
89+
fn split(&self, bots: &[Bot]) -> [Self; 8] {
90+
let half = (self.size / 2) as i32;
91+
let xs = [
92+
(self.min.x, self.min.x + half - 1),
93+
(self.min.x + half, self.max.x),
94+
];
95+
let ys = [
96+
(self.min.y, self.min.y + half - 1),
97+
(self.min.y + half, self.max.y),
98+
];
99+
let zs = [
100+
(self.min.z, self.min.z + half - 1),
101+
(self.min.z + half, self.max.z),
102+
];
103+
104+
std::array::from_fn(|i| {
105+
let (min_x, max_x) = xs[i >> 2];
106+
let (min_y, max_y) = ys[(i >> 1) & 1];
107+
let (min_z, max_z) = zs[i & 1];
108+
Self::new(
109+
Vec3::new(min_x, min_y, min_z),
110+
Vec3::new(max_x, max_y, max_z),
111+
bots,
112+
)
113+
})
114+
}
115+
}
116+
117+
impl Ord for Subcube {
118+
#[inline]
119+
fn cmp(&self, other: &Self) -> Ordering {
120+
// Sort by bot count ascending, dist descending, size descending.
121+
// When used inside a MaxHeap this will order entries by the reverse (bot count descending,
122+
// dist ascending, size ascending), which ensures the first point visited is optimal.
123+
self.bot_count
124+
.cmp(&other.bot_count)
125+
.then(self.dist.cmp(&other.dist).reverse())
126+
.then(self.size.cmp(&other.size).reverse())
127+
}
128+
}
129+
130+
impl PartialOrd for Subcube {
131+
#[inline]
132+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133+
Some(self.cmp(other))
134+
}
135+
}
136+
137+
examples!(Day23 -> (usize, u32) [
138+
{
139+
input: "pos=<0,0,0>, r=4\n\
140+
pos=<1,0,0>, r=1\n\
141+
pos=<4,0,0>, r=3\n\
142+
pos=<0,2,0>, r=1\n\
143+
pos=<0,5,0>, r=3\n\
144+
pos=<0,0,3>, r=1\n\
145+
pos=<1,1,1>, r=1\n\
146+
pos=<1,1,2>, r=1\n\
147+
pos=<1,3,1>, r=1",
148+
part1: 7,
149+
},
150+
{
151+
input: "pos=<10,12,12>, r=2\n\
152+
pos=<12,14,12>, r=2\n\
153+
pos=<16,12,12>, r=4\n\
154+
pos=<14,14,14>, r=6\n\
155+
pos=<50,50,50>, r=200\n\
156+
pos=<10,10,10>, r=5",
157+
part2: 36,
158+
},
159+
]);

crates/year2018/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ utils::year!(2018 => year2018, ${
2626
20 => day20::Day20,
2727
21 => day21::Day21,
2828
22 => day22::Day22,
29+
23 => day23::Day23,
2930
});

0 commit comments

Comments
 (0)