-
Notifications
You must be signed in to change notification settings - Fork 3
/
day_24.rs
157 lines (125 loc) Β· 4.5 KB
/
day_24.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use std::{fmt::Display, ops::RangeInclusive};
use common::{Answer, Solution};
use itertools::Itertools;
use nd_vec::{vector, Vec2, Vec3};
use num_traits::Num;
pub struct Day24;
impl Solution for Day24 {
fn name(&self) -> &'static str {
"Never Tell Me The Odds"
}
fn part_a(&self, input: &str) -> Answer {
let stones = parse(input);
solve_a(&stones, 200000000000000.0..=400000000000000.0).into()
}
fn part_b(&self, input: &str) -> Answer {
let stones = parse(input);
const BRUTE_RANGE: RangeInclusive<i64> = -1000..=1000;
let mut possible_x_vel = Vec::new();
let mut possible_y_vel = Vec::new();
let mut possible_z_vel = Vec::new();
let mut iter = stones.iter().tuple_combinations();
while possible_x_vel.len() != 1 || possible_y_vel.len() != 1 || possible_z_vel.len() != 1 {
let (a, b) = iter.next().expect("No solution found");
let process = |possible: &mut Vec<i64>, idx: usize| {
let pos = (a.pos.as_slice()[idx], b.pos.as_slice()[idx]);
let vel = (a.vel.as_slice()[idx], b.vel.as_slice()[idx]);
if vel.0 != vel.1 {
return;
}
let delta = (pos.0 - pos.1).abs();
let this = BRUTE_RANGE
.clone()
.filter(|i| i != &vel.0 && delta % (i - vel.0) == 0)
.collect_vec();
possible.retain(|v| this.contains(v));
if possible.is_empty() {
possible.extend(this);
}
};
process(&mut possible_x_vel, 0);
process(&mut possible_y_vel, 1);
process(&mut possible_z_vel, 2);
}
let (a, b) = (stones[0].as_float(), stones[1].as_float());
let (xv, yv, zv) = (
possible_x_vel[0] as f64,
possible_y_vel[0] as f64,
possible_z_vel[0] as f64,
);
let ma = (a.vel.y() - yv) / (a.vel.x() - xv);
let mb = (b.vel.y() - yv) / (b.vel.x() - xv);
let ca = a.pos.y() - ma * a.pos.x();
let cb = b.pos.y() - mb * b.pos.x();
let x = (cb - ca) / (ma - mb);
let y = ma * x + ca;
let t = (x - a.pos.x()) / (a.vel.x() - xv);
let z = a.pos.z() + (a.vel.z() - zv) * t;
((x + y + z) as i64).into()
}
}
fn solve_a(stones: &[HailStone<i64>], range: RangeInclusive<f64>) -> usize {
stones
.iter()
.tuple_combinations()
.filter_map(|(a, b)| a.collision(b))
.filter(|&pos| range.contains(&pos.x()) && range.contains(&pos.y()))
.count()
}
#[derive(Debug, Clone, Copy)]
struct HailStone<T: Num + Copy + Display> {
pos: Vec3<T>,
vel: Vec3<T>,
}
impl HailStone<i64> {
fn as_float(&self) -> HailStone<f64> {
HailStone {
pos: self.pos.num_cast().unwrap(),
vel: self.vel.num_cast().unwrap(),
}
}
fn collision(&self, other: &Self) -> Option<Vec2<f64>> {
let (a, b) = (self.as_float(), other.as_float());
let a_slope = a.vel.y() / a.vel.x();
let a_intercept = a.pos.y() - a_slope * a.pos.x();
let b_slope = b.vel.y() / b.vel.x();
let b_intercept = b.pos.y() - b_slope * b.pos.x();
let x_pos = (b_intercept - a_intercept) / (a_slope - b_slope);
let y_pos = a_slope * x_pos + a_intercept;
(x_pos.is_normal()
&& y_pos.is_normal()
&& !(a.vel.x() > 0.0 && x_pos < a.pos.x())
&& !(a.vel.x() < 0.0 && x_pos > a.pos.x())
&& !(b.vel.x() > 0.0 && x_pos < b.pos.x())
&& !(b.vel.x() < 0.0 && x_pos > b.pos.x()))
.then(|| vector!(x_pos, y_pos))
}
}
fn parse(input: &str) -> Vec<HailStone<i64>> {
let mut out = Vec::new();
for line in input.lines() {
let parts = line.split_whitespace().collect::<Vec<_>>();
let parse = |i| parts[i as usize].trim_end_matches(',').parse().unwrap();
out.push(HailStone {
pos: vector!(parse(0), parse(1), parse(2)),
vel: vector!(parse(4), parse(5), parse(6)),
});
}
out
}
#[cfg(test)]
mod test {
use indoc::indoc;
use super::{parse, solve_a};
const CASE: &str = indoc! {"
19, 13, 30 @ -2, 1, -2
18, 19, 22 @ -1, -1, -2
20, 25, 34 @ -2, -2, -4
12, 31, 28 @ -1, -2, -1
20, 19, 15 @ 1, -5, -3
"};
#[test]
fn part_a() {
assert_eq!(solve_a(&parse(CASE), 7.0..=37.0), 2);
}
}