4 Commits

Author SHA1 Message Date
6506af879a Simplify y-hit iterator 2021-12-18 18:15:11 +01:00
101ebee505 Use dedicated iterator instead of range 2021-12-18 18:15:11 +01:00
cc81a7012b Use math instead of binary search 2021-12-18 17:21:43 +01:00
9c299f140c Tighter bounds for the range of y 2021-12-18 15:01:47 +01:00

View File

@@ -1,8 +1,6 @@
use std::io::Read; use std::io::Read;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use itertools::Itertools;
use itertools::MinMaxResult;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::combinator::map; use nom::combinator::map;
use nom::sequence::preceded; use nom::sequence::preceded;
@@ -19,7 +17,11 @@ fn solve_quadratic(a: f64, b: f64, c: f64) -> Option<f64> {
None None
} else { } else {
// Don't care about the smaller solution due to problem statement // Don't care about the smaller solution due to problem statement
Some((-b - d.sqrt()) / 2. / a) if a > 0. {
Some((-b + d.sqrt()) / 2. / a)
} else {
Some((-b - d.sqrt()) / 2. / a)
}
} }
} }
@@ -27,70 +29,49 @@ fn position(initial: i32, time: i32) -> i32 {
time * (2 * initial - time + 1) / 2 time * (2 * initial - time + 1) / 2
} }
fn find_hit(initial: i32, range: &RangeInclusive<i32>) -> Option<RangeInclusive<i32>> { fn find_hit(initial: i32, range: &RangeInclusive<i32>) -> impl Iterator<Item = i32> + '_ {
// y position at time x: f(x) = x * (1 + initial + initial - x) / 2 // y position at time x: f(x) = x * (1 + initial + initial - x) / 2
// = -1/2x^2 + (initial + 0.5)x // = -1/2x^2 + (initial + 0.5)x
// //
// to hit, find x := (max(box) + min(box)) / 2 = f(x) // to hit, find x := (max(box) + min(box)) / 2 = f(x)
// = -1/2x^2 + (initial + 0.5)x // = -1/2x^2 + (initial + 0.5)x
// -1/2x^2 + (initial + 0.5)x - (max(box) + min(box)) / 2 = 0 // -1/2x^2 + (initial + 0.5)x - (max(box) + min(box)) / 2 = 0
let middle = (*range.start() + *range.end()) as f64 / 2.; let middle = *range.start() as f64;
let b = initial as f64 + 0.5; let b = initial as f64 + 0.5;
let hit = solve_quadratic(-0.5, b, -middle)? as i32; let hit = if let Some(hit) = solve_quadratic(-0.5, b, -middle) {
hit as i32
if hit < 0 {
// Should not happen because of the shape but for correctness
None
} else { } else {
let min_hit = (0..=hit) // Cause an empty range
.rev() -1
.take_while(|&n| range.contains(&position(initial, n))) };
.min();
let max_hit = ((hit + 1)..) (0..=hit)
.take_while(|&n| range.contains(&position(initial, n))) .rev()
.max(); .take_while(move |&n| range.contains(&position(initial, n)))
match (min_hit, max_hit) {
(Some(min), Some(max)) => Some(min..=max),
(Some(val), None) | (None, Some(val)) => Some(val..=val),
_ => None,
}
}
} }
fn find_speed(x: i32, range: &RangeInclusive<i32>) -> Option<RangeInclusive<i32>> { fn find_speed(x: i32, range: &RangeInclusive<i32>) -> Option<(i32, i32)> {
let mut min = 0; if *range.end() <= position(x, x) {
let mut max = *range.end(); // Can and should come to a full stop
let max = solve_quadratic(0.5, 0.5, -*range.end() as f64)? as i32;
// Need to tweak the formula as x slows down let min = (0..=max)
let x_pos = |speed| position(speed, speed.min(x)); .rev()
.take_while(|&n| range.contains(&position(n, n)))
.last()?;
while max >= min { Some((min, max))
let speed = (max + min) / 2; } else {
// Might hit the target at speed
let max = (x * x + 2 * *range.end() - x) / (2 * x);
let pos = x_pos(speed); let min = (0..=max)
.rev()
.take_while(|&n| range.contains(&position(n, n.min(x))))
.last()?;
if range.contains(&x_pos(speed)) { Some((min, max))
let min_speed = (0..speed)
.rev()
.take_while(|&speed| range.contains(&x_pos(speed)))
.min()
.unwrap_or(speed);
let max_speed = ((speed + 1)..)
.take_while(|&speed| range.contains(&x_pos(speed)))
.max()
.unwrap_or(speed);
return Some(min_speed..=max_speed);
} else if pos < *range.start() {
min = speed + 1;
} else {
max = speed - 1;
}
} }
None
} }
fn parse_range(input: &[u8]) -> IResult<&[u8], RangeInclusive<i32>> { fn parse_range(input: &[u8]) -> IResult<&[u8], RangeInclusive<i32>> {
@@ -111,40 +92,35 @@ fn parse_input(input: &[u8]) -> IResult<&[u8], (RangeInclusive<i32>, RangeInclus
pub fn part1(input: &mut dyn Read) -> String { pub fn part1(input: &mut dyn Read) -> String {
let (x_range, y_range) = read_input(input, parse_input); let (x_range, y_range) = read_input(input, parse_input);
let check_value = |y_speed| { let check_value =
let mut time = find_hit(y_speed, &y_range)?; |y_speed| find_hit(y_speed, &y_range).any(|time| find_speed(time, &x_range).is_some());
if time.any(|time| find_speed(time, &x_range).is_some()) { debug_assert!(*y_range.start() < 0);
Some(position(y_speed, y_speed)) let y_max = -*y_range.start();
} else {
None
}
};
(0..1000).filter_map(check_value).max().unwrap().to_string() (0..y_max)
.rev()
.find(|&speed| check_value(speed))
.map(|speed| position(speed, speed))
.unwrap()
.to_string()
} }
pub fn part2(input: &mut dyn Read) -> String { pub fn part2(input: &mut dyn Read) -> String {
let (x_range, y_range) = read_input(input, parse_input); let (x_range, y_range) = read_input(input, parse_input);
let num_options = |y_speed| { let num_options = |y_speed| {
let time = find_hit(y_speed, &y_range)?; find_hit(y_speed, &y_range)
let range = time
.filter_map(|time| find_speed(time, &x_range)) .filter_map(|time| find_speed(time, &x_range))
.flat_map(|x| [*x.start(), *x.end()]) .reduce(|(a_min, a_max), (b_min, b_max)| (a_min.min(b_min), a_max.max(b_max)))
.minmax(); .map(|(min, max)| max - min + 1)
.unwrap_or(0)
Some(match range {
MinMaxResult::NoElements => 0,
MinMaxResult::OneElement(_) => 1,
MinMaxResult::MinMax(min, max) => max - min + 1,
})
}; };
(-1000..1000) debug_assert!(*y_range.start() < 0);
.filter_map(num_options) let y_max = -*y_range.start();
.sum::<i32>()
.to_string() (-y_max..y_max).map(num_options).sum::<i32>().to_string()
} }
#[cfg(test)] #[cfg(test)]
@@ -157,9 +133,9 @@ mod tests {
#[test] #[test]
fn test_find_hit() { fn test_find_hit() {
assert_eq!(find_hit(2, &(-10..=-5)), Some(7..=7)); assert_eq!(find_hit(2, &(-10..=-5)).collect::<Vec<_>>(), vec![7]);
assert_eq!(find_hit(3, &(-10..=-5)), Some(9..=9)); assert_eq!(find_hit(3, &(-10..=-5)).collect::<Vec<_>>(), vec![9]);
assert_eq!(find_hit(0, &(-10..=-5)), Some(4..=5)); assert_eq!(find_hit(0, &(-10..=-5)).collect::<Vec<_>>(), vec![5, 4]);
} }
#[test] #[test]