Skip to content

Commit 73bcd30

Browse files
committed
Year 2018 Day 6
1 parent 035ee9d commit 73bcd30

File tree

7 files changed

+316
-0
lines changed

7 files changed

+316
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
242242
| 3 | [No Matter How You Slice It](https://adventofcode.com/2018/day/3) | [Source](src/year2018/day03.rs) | 56 |
243243
| 4 | [Repose Record](https://adventofcode.com/2018/day/4) | [Source](src/year2018/day04.rs) | 46 |
244244
| 5 | [Alchemical Reduction](https://adventofcode.com/2018/day/5) | [Source](src/year2018/day05.rs) | 353 |
245+
| 6 | [Chronal Coordinates](https://adventofcode.com/2018/day/6) | [Source](src/year2018/day06.rs) | 38 |
245246

246247
## 2017
247248

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ mod year2018 {
134134
benchmark!(year2018, day03);
135135
benchmark!(year2018, day04);
136136
benchmark!(year2018, day05);
137+
benchmark!(year2018, day06);
137138
}
138139

139140
mod year2019 {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ pub mod year2018 {
117117
pub mod day03;
118118
pub mod day04;
119119
pub mod day05;
120+
pub mod day06;
120121
}
121122

122123
/// # Rescue Santa from deep space with a solar system adventure.

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ fn year2018() -> Vec<Solution> {
184184
solution!(year2018, day03),
185185
solution!(year2018, day04),
186186
solution!(year2018, day05),
187+
solution!(year2018, day06),
187188
]
188189
}
189190

src/year2018/day06.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
//! # Chronal Coordinates
2+
//!
3+
//! Both parts can be solved with a [BFS](https://en.wikipedia.org/wiki/Breadth-first_search)
4+
//! approach. The bounding box of the coordinates is roughly 300 wide by 300 high so the total
5+
//! complexity would be approximately `O(90,000)`.
6+
//!
7+
//! A much faster approach for both parts is a
8+
//! [sweep line algorithm](https://en.wikipedia.org/wiki/Sweep_line_algorithm). We sweep from
9+
//! top to bottom (minimum y coordinate to maximum y coordinate) computing the area a slice at a
10+
//! time. There are 50 coordinates so the complexity of this approach is much lower at
11+
//! approximately `O(300 * 50) = O(15000)`.
12+
use crate::util::iter::*;
13+
use crate::util::parse::*;
14+
use crate::util::point::*;
15+
16+
pub struct Input {
17+
min_y: i32,
18+
max_y: i32,
19+
points: Vec<Point>,
20+
}
21+
22+
/// Parse points while keeping track of the min and max y coordinates.
23+
pub fn parse(input: &str) -> Input {
24+
let mut min_y = i32::MAX;
25+
let mut max_y = i32::MIN;
26+
let points: Vec<_> = input
27+
.iter_signed()
28+
.chunk::<2>()
29+
.map(|[x, y]| {
30+
min_y = min_y.min(y);
31+
max_y = max_y.max(y);
32+
Point::new(x, y)
33+
})
34+
.collect();
35+
36+
Input { min_y, max_y, points }
37+
}
38+
39+
/// Sweep line approach computing the area of each *finite* coordinate. A coordinate has infinite
40+
/// area if any point on the edge of the bounding box formed by the minimum and maximum x and y
41+
/// coordinates is closest to that coordinate.
42+
///
43+
/// We sort the coordinates in ascending x value then for each row, compare the next coordinate
44+
/// against the head of a stack. This quickly eliminates coordinates that are further away at all
45+
/// points. Interestingly this approach is very similar to the previous [`Day 5`].
46+
///
47+
/// [`Day 5`]: crate::year2018::day05
48+
pub fn part1(input: &Input) -> i32 {
49+
let mut points = input.points.clone();
50+
let mut area = vec![0; points.len()];
51+
let mut finite = vec![true; points.len()];
52+
let mut candidates: Vec<(usize, i32, i32)> = Vec::new();
53+
54+
// Special value for coordinates that are equidistant from nearest neighbour.
55+
let marker = usize::MAX;
56+
57+
// Sorts points left to right so that ranges can be merged.
58+
points.sort_unstable_by_key(|p| p.x);
59+
60+
// Sweep top to bottom.
61+
for row in input.min_y..=input.max_y {
62+
// Left to right.
63+
for (j, &p) in points.iter().enumerate() {
64+
// Manhattan distance is the absolute difference in y coordinates since the x
65+
// coordinate is already identical.
66+
let m1 = (p.y - row).abs();
67+
let x1 = p.x;
68+
69+
loop {
70+
if let Some((i, m0, x0)) = candidates.pop() {
71+
// Compare against the head of the stack.
72+
let delta = m1 - m0;
73+
let width = x1 - x0;
74+
75+
if delta < -width {
76+
// Left coordinate is further away at every points.
77+
// Discard and pop next left coordinate from the stack.
78+
//
79+
// rrrrrrrrrrrrrrrr <-- Considering only this row
80+
// ....R...........
81+
// ................
82+
// ................
83+
// ..L.............
84+
continue;
85+
} else if delta == -width {
86+
// Left coordinate is equal from its center leftwards
87+
// Replace with special marker value.
88+
//
89+
// ...rrrrrrrrrrrrr
90+
// ....R...........
91+
// ................
92+
// ..L.............
93+
candidates.push((marker, m0, x0));
94+
candidates.push((j, m1, x1));
95+
} else if delta == width {
96+
// Right coordinate is equal from its center rightwards.
97+
// Replace with special marker value.
98+
//
99+
// llll............
100+
// ..L.............
101+
// ................
102+
// ....R...........
103+
candidates.push((i, m0, x0));
104+
candidates.push((marker, m1, x1));
105+
} else if delta > width {
106+
// Right coordinate is further away at every point.
107+
// Discard then check next right coordinate from points.
108+
//
109+
// llllllllllllllll
110+
// ..L.............
111+
// ................
112+
// ................
113+
// ....R...........
114+
candidates.push((i, m0, x0));
115+
} else {
116+
// Coordinates split the distance, some points closer to left and others
117+
// closer to right. Add both to candidates.
118+
//
119+
// lllll.rrrrrrrrrr
120+
// .........R......
121+
// ..L.............
122+
// ................
123+
// ................
124+
candidates.push((i, m0, x0));
125+
candidates.push((j, m1, x1));
126+
}
127+
} else {
128+
// Nothing on stack to compare with, push coordinate.
129+
candidates.push((j, m1, x1));
130+
}
131+
132+
break;
133+
}
134+
}
135+
136+
// Any coordinates that are closest to the bounding box edges are infinite.
137+
let left = candidates[0].0;
138+
if left != marker {
139+
finite[left] = false;
140+
}
141+
142+
let right = candidates[candidates.len() - 1].0;
143+
if right != marker {
144+
finite[right] = false;
145+
}
146+
147+
// Only consider finite coordinates.
148+
for window in candidates.windows(3) {
149+
let (_, m0, x0) = window[0];
150+
let (i, m1, x1) = window[1];
151+
let (_, m2, x2) = window[2];
152+
153+
// Skip coordinates where all points are equally distant from their neighbor.
154+
if i != marker {
155+
if row == input.min_y || row == input.max_y {
156+
// All coordinates the are closest to the top or bottom row are infinite.
157+
finite[i] = false;
158+
} else {
159+
// Count points closest to the left, to the right and the coordinate itself.
160+
let left = (x1 - x0 + m0 - m1 - 1) / 2;
161+
let right = (x2 - x1 + m2 - m1 - 1) / 2;
162+
area[i] += left + 1 + right;
163+
}
164+
}
165+
}
166+
167+
candidates.clear();
168+
}
169+
170+
// Find largest area closest to finite coordinate.
171+
(0..points.len()).filter_map(|i| finite[i].then_some(area[i])).max().unwrap()
172+
}
173+
174+
pub fn part2(input: &Input) -> i32 {
175+
part2_testable(input, 10_000)
176+
}
177+
178+
/// Sweep from top to bottom to find the size of the roughly circular area that is less than
179+
/// a specified maximum distance from all other points.
180+
///
181+
/// Finding the center of this circle to act as a starting point is an interesting sub-problem.
182+
/// The two dimensional [geometric median](https://en.wikipedia.org/wiki/Geometric_median) that
183+
/// minimizes the Euclidean distance to all other points has no general closed form formula.
184+
/// The [centroid](https://en.wikipedia.org/wiki/Centroid) is close but not exact as it minimizes
185+
/// the distance *squared*.
186+
///
187+
/// However the Manhattan distance is independent for each axis, so we can instead solve for the
188+
/// one dimensional case. This is the [median](https://en.wikipedia.org/wiki/Median) of each axis.
189+
/// Intuitively this makes sense, as the median has the same number of points on either side,
190+
/// so moving either direction, the increase from half the points is cancelled out by the decrease
191+
/// of the other half of the points.
192+
///
193+
/// The algorithm is:
194+
/// * Find center
195+
/// * Go upwards from center until top edge of circle reached.
196+
/// * For each row of circle, find left and right extents
197+
/// * Add area of row to total, then advance to row below.
198+
pub fn part2_testable(input: &Input, max_distance: i32) -> i32 {
199+
// Sort points in ascending order in order to find median.
200+
let mut xs: Vec<_> = input.points.iter().map(|p| p.x).collect();
201+
xs.sort_unstable();
202+
203+
let mut ys: Vec<_> = input.points.iter().map(|p| p.y).collect();
204+
ys.sort_unstable();
205+
206+
// Find coordinate closest to median point.
207+
let x = xs[xs.len() / 2];
208+
let mut y = ys[ys.len() / 2];
209+
210+
// Calculate minimum distance.
211+
let median = Point::new(x, y);
212+
let mut y_distance: i32 = input.points.iter().map(|o| o.manhattan(median)).sum();
213+
214+
// Find top of region
215+
while y_distance + prev(&ys, y) < max_distance {
216+
y_distance += prev(&ys, y);
217+
y -= 1;
218+
}
219+
220+
let mut left = x;
221+
let mut left_dist = y_distance;
222+
let mut right = x;
223+
let mut right_dist = y_distance;
224+
let mut area = 0;
225+
226+
// Sweep top to bottom.
227+
while y_distance < max_distance {
228+
// Expand moving left edge to the left
229+
while left_dist < max_distance {
230+
left_dist += prev(&xs, left);
231+
left -= 1;
232+
}
233+
// Contract moving left edge to the right
234+
while left_dist >= max_distance {
235+
left_dist += next(&xs, left);
236+
left += 1;
237+
}
238+
// Expand moving right edge to the right
239+
while right_dist < max_distance {
240+
right_dist += next(&xs, right);
241+
right += 1;
242+
}
243+
// Contract moving right edge to the left
244+
while right_dist >= max_distance {
245+
right_dist += prev(&xs, right);
246+
right -= 1;
247+
}
248+
249+
// Move downwards one row.
250+
let next = next(&ys, y);
251+
y_distance += next;
252+
left_dist += next;
253+
right_dist += next;
254+
255+
y += 1;
256+
area += right - left + 1;
257+
}
258+
259+
area
260+
}
261+
262+
/// Calculate the change in distance moving left or up.
263+
fn prev(slice: &[i32], n: i32) -> i32 {
264+
let mut total = 0;
265+
266+
for &s in slice {
267+
if s >= n {
268+
total += 1;
269+
} else {
270+
total -= 1;
271+
}
272+
}
273+
274+
total
275+
}
276+
277+
/// Calculate the change in distance moving down or right.
278+
fn next(slice: &[i32], n: i32) -> i32 {
279+
let mut total = 0;
280+
281+
for &s in slice {
282+
if s <= n {
283+
total += 1;
284+
} else {
285+
total -= 1;
286+
}
287+
}
288+
289+
total
290+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ mod year2018 {
118118
mod day03_test;
119119
mod day04_test;
120120
mod day05_test;
121+
mod day06_test;
121122
}
122123

123124
mod year2019 {

tests/year2018/day06_test.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use aoc::year2018::day06::*;
2+
3+
const EXAMPLE: &str = "\
4+
1, 1
5+
1, 6
6+
8, 3
7+
3, 4
8+
5, 5
9+
8, 9";
10+
11+
#[test]
12+
fn part1_test() {
13+
let input = parse(EXAMPLE);
14+
assert_eq!(part1(&input), 17);
15+
}
16+
17+
#[test]
18+
fn part2_test() {
19+
let input = parse(EXAMPLE);
20+
assert_eq!(part2_testable(&input, 32), 16);
21+
}

0 commit comments

Comments
 (0)