day 15 finished

This commit is contained in:
Ruediger Ludwig 2023-02-13 19:30:59 +01:00
parent a1f97bac83
commit 3b12de4970
5 changed files with 384 additions and 1 deletions

15
data/day15/example01.txt Normal file
View file

@ -0,0 +1,15 @@
10
Sensor at x=2, y=18: closest beacon is at x=-2, y=15
Sensor at x=9, y=16: closest beacon is at x=10, y=16
Sensor at x=13, y=2: closest beacon is at x=15, y=3
Sensor at x=12, y=14: closest beacon is at x=10, y=16
Sensor at x=10, y=20: closest beacon is at x=10, y=16
Sensor at x=14, y=17: closest beacon is at x=10, y=16
Sensor at x=8, y=7: closest beacon is at x=2, y=10
Sensor at x=2, y=0: closest beacon is at x=2, y=10
Sensor at x=0, y=11: closest beacon is at x=2, y=10
Sensor at x=20, y=14: closest beacon is at x=25, y=17
Sensor at x=17, y=20: closest beacon is at x=21, y=22
Sensor at x=16, y=7: closest beacon is at x=15, y=3
Sensor at x=14, y=3: closest beacon is at x=15, y=3
Sensor at x=20, y=1: closest beacon is at x=15, y=3

32
data/day15/input.txt Normal file
View file

@ -0,0 +1,32 @@
2000000
Sensor at x=3859432, y=2304903: closest beacon is at x=3677247, y=3140958
Sensor at x=2488890, y=2695345: closest beacon is at x=1934788, y=2667279
Sensor at x=3901948, y=701878: closest beacon is at x=4095477, y=368031
Sensor at x=2422190, y=1775708: closest beacon is at x=1765036, y=2000000
Sensor at x=2703846, y=3282799: closest beacon is at x=2121069, y=3230302
Sensor at x=172003, y=2579074: closest beacon is at x=-77667, y=3197309
Sensor at x=1813149, y=1311283: closest beacon is at x=1765036, y=2000000
Sensor at x=1704453, y=2468117: closest beacon is at x=1934788, y=2667279
Sensor at x=1927725, y=2976002: closest beacon is at x=1934788, y=2667279
Sensor at x=3176646, y=1254463: closest beacon is at x=2946873, y=2167634
Sensor at x=2149510, y=3722117: closest beacon is at x=2121069, y=3230302
Sensor at x=3804434, y=251015: closest beacon is at x=4095477, y=368031
Sensor at x=2613561, y=3932220: closest beacon is at x=2121069, y=3230302
Sensor at x=3997794, y=3291220: closest beacon is at x=3677247, y=3140958
Sensor at x=98328, y=3675176: closest beacon is at x=-77667, y=3197309
Sensor at x=2006541, y=2259601: closest beacon is at x=1934788, y=2667279
Sensor at x=663904, y=122919: closest beacon is at x=1618552, y=-433244
Sensor at x=1116472, y=3349728: closest beacon is at x=2121069, y=3230302
Sensor at x=2810797, y=2300748: closest beacon is at x=2946873, y=2167634
Sensor at x=1760767, y=2024355: closest beacon is at x=1765036, y=2000000
Sensor at x=3098487, y=2529092: closest beacon is at x=2946873, y=2167634
Sensor at x=1716839, y=634872: closest beacon is at x=1618552, y=-433244
Sensor at x=9323, y=979154: closest beacon is at x=-245599, y=778791
Sensor at x=1737623, y=2032367: closest beacon is at x=1765036, y=2000000
Sensor at x=26695, y=3049071: closest beacon is at x=-77667, y=3197309
Sensor at x=3691492, y=3766350: closest beacon is at x=3677247, y=3140958
Sensor at x=730556, y=1657010: closest beacon is at x=1765036, y=2000000
Sensor at x=506169, y=3958647: closest beacon is at x=-77667, y=3197309
Sensor at x=2728744, y=23398: closest beacon is at x=1618552, y=-433244
Sensor at x=3215227, y=3077078: closest beacon is at x=3677247, y=3140958
Sensor at x=2209379, y=3030851: closest beacon is at x=2121069, y=3230302

View file

@ -22,6 +22,10 @@ impl<T> Pos<T>
where
T: Num + Copy,
{
pub fn splat(v: T) -> Pos<T> {
Pos(v, v)
}
pub fn x(&self) -> T {
self.0
}
@ -259,3 +263,16 @@ where
Pos::mul(*self, *rhs)
}
}
impl<T> Pos<T>
where
T: Num + Signed + Copy,
{
pub fn taxicab_origin(&self) -> T {
self.0.abs() + self.1.abs()
}
pub fn taxicab(&self, other: &Pos<T>) -> T {
(self.0 - other.0).abs() + (self.1 - other.1).abs()
}
}

317
src/days/day15/mod.rs Normal file
View file

@ -0,0 +1,317 @@
use super::template::{DayTrait, ResultType};
use crate::common::pos::Pos;
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use std::{collections::HashSet, num::ParseIntError};
use thiserror::Error;
const DAY_NUMBER: usize = 15;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let row = lines[0].parse()?;
let (sensors, beacons) = Day::parse_all(&lines[1..])?;
let result = Day::count_coverage_at(&sensors, &beacons, row);
Ok(ResultType::Integer(result))
}
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let (sensors, _) = Day::parse_all(&lines[1..])?;
let mid_lines = Day::dividing_lines(&sensors);
let points = mid_lines
.iter()
.tuple_combinations()
.filter_map(|(first, second)| first.cross(second))
.filter(|pos| sensors.iter().all(|sensor| !sensor.contains(pos)))
.dedup()
.collect::<Vec<_>>();
if points.len() != 1 {
Err(SensorError::NotExactlyOneResult(points.len()))?;
}
let point = points.first().unwrap();
Ok(ResultType::Integer(point.x() * 4000000 + point.y()))
}
}
impl Day {
fn parse_all(lines: &[String]) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
let mut sensors = HashSet::new();
let mut beacons = HashSet::new();
for line in lines {
let (sensor, beacon) = Sensor::parse(line)?;
sensors.insert(sensor);
beacons.insert(beacon);
}
Ok((sensors, beacons))
}
fn count_coverage_at(sensors: &HashSet<Sensor>, beacons: &HashSet<Pos<i64>>, row: i64) -> i64 {
let ranges = sensors
.iter()
.filter_map(|sensor| sensor.range_at(row))
.collect::<Vec<_>>();
let merged: i64 = Range::merge_all(&ranges)
.iter()
.map(|range| range.width())
.sum();
let sensors = sensors
.iter()
.filter(|sensor| sensor.pos.y() == row)
.count() as i64;
let beacons = beacons.iter().filter(|beacons| beacons.y() == row).count() as i64;
merged - (sensors + beacons)
}
fn dividing_lines(sensors: &HashSet<Sensor>) -> Vec<Line> {
sensors
.iter()
.tuple_combinations()
.filter_map(|(first, second)| Line::create(first, second))
.collect()
}
}
#[derive(Debug, Error)]
enum SensorError {
#[error("Not an Integer")]
NotAnInt(#[from] ParseIntError),
#[error("Unknown line: {0}")]
UnknownLine(String),
#[error("Did not find exactly one point for the sensor: {0}")]
NotExactlyOneResult(usize),
}
#[derive(Debug)]
struct Line {
start: Pos<i64>,
is_up: bool,
steps: i64,
}
impl Line {
pub fn create(first: &Sensor, second: &Sensor) -> Option<Line> {
if first.border_distance(second) != 2 {
return None;
}
let (one, two) = if first.pos.x() < second.pos.x() {
(first, second)
} else {
(second, first)
};
let start;
let is_up;
if one.pos.y() < two.pos.y() {
is_up = true;
if one.pos.y() + one.radius <= two.pos.y() {
start = Pos::new(one.pos.x(), one.pos.y() + one.radius + 1);
} else {
start = Pos::new(two.pos.x() - two.radius - 1, two.pos.y());
}
} else {
is_up = false;
if one.pos.y() - one.radius >= two.pos.y() {
start = Pos::new(one.pos.x(), one.pos.y() - one.radius - 1);
} else {
start = Pos::new(two.pos.x() - two.radius - 1, two.pos.y());
}
}
let steps = two.pos.x().min(one.pos.x() + one.radius) - start.x();
Some(Line {
start,
is_up,
steps,
})
}
fn cross(&self, other: &Line) -> Option<Pos<i64>> {
if self.is_up == other.is_up {
return None;
}
let (bottom_up, top_down) = if self.is_up {
(self, other)
} else {
(other, self)
};
let r2 =
bottom_up.start.x() + bottom_up.start.y() - (top_down.start.x() + top_down.start.y());
if r2 % 2 != 0 {
return None;
}
let r = r2 / 2;
if r < 0 || r > top_down.steps {
return None;
}
let pos = top_down.start + Pos::splat(r);
Some(pos)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Range(i64, i64);
impl Range {
fn overlaps(&self, other: &Range) -> bool {
self.0 <= other.1 && other.0 <= self.1
}
pub fn width(&self) -> i64 {
self.1 - self.0 + 1
}
pub fn merge_all(ranges: &[Range]) -> Vec<Range> {
let mut ranges: Vec<Range> = ranges.iter().sorted().copied().collect();
assert!(!ranges.is_empty());
loop {
let mut next = vec![];
let mut merged = false;
let mut current = ranges[0];
for range in &ranges[1..] {
if current.overlaps(range) {
current = Range(current.0, current.1.max(range.1));
merged = true;
} else {
next.push(current);
current = *range;
}
}
next.push(current);
if merged {
ranges = next;
} else {
return next;
}
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
struct Sensor {
pos: Pos<i64>,
radius: i64,
}
lazy_static! {
static ref SENSOR: Regex = Regex::new(r"x=(-?\d+), y=(-?\d+).*x=(-?\d+), y=(-?\d+)").unwrap();
}
impl Sensor {
pub fn parse(line: &str) -> Result<(Sensor, Pos<i64>), SensorError> {
let caps = SENSOR
.captures(line)
.ok_or(SensorError::UnknownLine(line.to_owned()))?;
let sensor_x = caps.get(1).unwrap().as_str().parse()?;
let sensor_y = caps.get(2).unwrap().as_str().parse()?;
let beacon_x = caps.get(3).unwrap().as_str().parse()?;
let beacon_y = caps.get(4).unwrap().as_str().parse()?;
let sensor = Pos::new(sensor_x, sensor_y);
let beacon = Pos::new(beacon_x, beacon_y);
let radius = sensor.taxicab(&beacon);
Ok((
Sensor {
pos: sensor,
radius,
},
beacon,
))
}
pub fn range_at(&self, y: i64) -> Option<Range> {
let distance = (self.pos.y() - y).abs();
if distance > self.radius {
None
} else {
let extent = self.radius - distance;
Some(Range(self.pos.x() - extent, self.pos.x() + extent))
}
}
pub fn border_distance(&self, other: &Sensor) -> i64 {
let distance = self.pos.taxicab(&other.pos);
distance - self.radius - other.radius
}
pub fn contains(&self, pos: &Pos<i64>) -> bool {
self.pos.taxicab(pos) <= self.radius
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_lines;
use anyhow::Result;
#[test]
fn test_parse() -> Result<()> {
let input = "Sensor at x=2, y=18: closest beacon is at x=-2, y=15";
let expected = (
Sensor {
pos: Pos::new(2, 18),
radius: 7,
},
Pos::new(-2, 15),
);
let result = Sensor::parse(input)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_width() {
let sensor = Sensor {
pos: Pos::new(8, 7),
radius: 9,
};
assert_eq!(sensor.range_at(17), None);
assert_eq!(sensor.range_at(7), Some(Range(-1, 17)));
assert_eq!(sensor.range_at(10), Some(Range(2, 14)));
}
#[test]
fn test_merge() {
let input = vec![Range(4, 10), Range(1, 3), Range(5, 9), Range(8, 12)];
let expected = vec![Range(1, 3), Range(4, 12)];
assert_eq!(Range::merge_all(&input), expected);
}
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(26);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(56000011);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
}

View file

@ -12,6 +12,7 @@ mod day11;
mod day12;
mod day13;
mod day14;
mod day15;
mod template;
pub use template::DayTrait;
@ -21,7 +22,7 @@ pub mod day_provider {
use super::*;
use thiserror::Error;
const MAX_DAY: usize = 14;
const MAX_DAY: usize = 15;
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
match day_num {
@ -39,6 +40,7 @@ pub mod day_provider {
12 => Ok(Box::new(day12::Day)),
13 => Ok(Box::new(day13::Day)),
14 => Ok(Box::new(day14::Day)),
15 => Ok(Box::new(day15::Day)),
_ => Err(ProviderError::InvalidNumber(day_num)),
}
}