1
0
Fork 0

day(15): add solution

Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
Matej Focko 2023-01-03 22:15:50 +01:00
parent f7840f5eae
commit c087e388ac
Signed by: mfocko
GPG key ID: 7C47D46246790496
4 changed files with 401 additions and 0 deletions

106
Cargo.lock generated
View file

@ -33,11 +33,18 @@ dependencies = [
"color-eyre",
"itertools",
"lazy_static",
"rayon",
"regex",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.66"
@ -92,6 +99,49 @@ dependencies = [
"tracing-error",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "either"
version = "1.8.0"
@ -114,6 +164,15 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indenter"
version = "0.3.3"
@ -171,6 +230,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.5.4"
@ -190,6 +258,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "num_cpus"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.6"
@ -250,6 +328,28 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.7.0"
@ -282,6 +382,12 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.149"

View file

@ -13,6 +13,8 @@ color-eyre = "0.6.2"
tracing-subscriber = { version = "0.3.16", default-features = true, features = ["env-filter", "local-time"] }
regex = "1.7.0"
lazy_static = "1.4.0"
rayon = "1.6.1"
# bitvec = "1.0.1"
[profile.dev.package.backtrace]
opt-level = 3

14
samples/day15.txt Normal file
View file

@ -0,0 +1,14 @@
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

279
src/bin/day15.rs Normal file
View file

@ -0,0 +1,279 @@
use std::{
cmp::{max, min},
collections::BTreeMap,
str::FromStr,
};
use aoc_2022::*;
use color_eyre::Report;
use itertools::Itertools;
use lazy_static::lazy_static;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use regex::Regex;
// use tracing::debug;
type Input = Vec<Sensor>;
type Output = usize;
type Coord = Vector2D<i32>;
fn manhattan(u: &Coord, v: &Coord) -> i32 {
let direction = *v - *u;
(*direction.x()).abs() + (*direction.y()).abs()
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct Sensor {
position: Coord,
beacon: Coord,
}
impl Sensor {
fn bounds_at_y(&self, y: i32) -> Option<(Coord, Coord)> {
let m = manhattan(&self.position, &self.beacon);
if y < *self.position.y() - m || y > *self.position.y() + m {
return None;
}
let x = *self.position.x();
let side_d = m - (*self.position.y() - y).abs();
Some((Coord::new(x - side_d, y), Coord::new(x + side_d, y)))
}
}
lazy_static! {
static ref REGEX: Regex = Regex::new(concat!(
r"Sensor at x=(?P<sx>-?\d+), y=(?P<sy>-?\d+): ",
r"closest beacon is at x=(?P<bx>-?\d+), y=(?P<by>-?\d+)"
))
.unwrap();
}
impl FromStr for Sensor {
type Err = Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let caps = REGEX.captures(s).unwrap();
let (sx, sy, bx, by) = vec!["sx", "sy", "bx", "by"]
.iter()
.map(|&p| caps[p].parse::<i32>().unwrap())
.collect_tuple()
.unwrap();
Ok(Sensor {
position: Coord::new(sx, sy),
beacon: Coord::new(bx, by),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
enum Status {
Sensor,
Beacon,
Cannot,
}
fn cannot_contain(sensors: &Input, watched_y: i32) -> Output {
let mut positions = BTreeMap::<i32, Status>::new();
// mark unusable positions
sensors
.iter()
.filter_map(|s| s.bounds_at_y(watched_y))
.for_each(|(l, r)| {
(*l.x()..=*r.x()).for_each(|x| {
positions.insert(x, Status::Cannot);
})
});
// rewrite beacons and sensors if needed
sensors
.iter()
.filter(|s| *s.position.y() == watched_y || *s.beacon.y() == watched_y)
.for_each(|s| {
if *s.beacon.y() == watched_y {
positions.insert(*s.beacon.x(), Status::Beacon);
}
if *s.position.y() == watched_y {
positions.insert(*s.beacon.x(), Status::Sensor);
}
});
// count unusable
positions
.iter()
.filter(|(_, s)| **s == Status::Cannot)
.count()
}
fn check_row(sensors: &Input, watched_y: i32, upper_bound: i32) -> Vec<(i32, i32)> {
let mut positions: Vec<(i32, i32)> = Vec::new();
// mark unusable positions
positions.extend(
sensors
.iter()
.filter_map(|s| s.bounds_at_y(watched_y))
.map(|(l, r)| (max(0, *l.x()), min(*r.x(), upper_bound))),
);
positions.sort();
positions
}
fn find_first_empty(positions: &Vec<(i32, i32)>, upper_bound: i32) -> Option<i32> {
let mut x = 0;
for &(from, to) in positions {
if x >= from && x <= to {
x = to + 1;
}
}
if x <= upper_bound {
return Some(x);
}
None
}
fn find_distress(sensors: &Input, upper_bound: usize) -> Output {
let (x, y) = (0..=upper_bound)
.into_par_iter()
.find_map_any(|y| {
let positions = check_row(sensors, y as i32, upper_bound as i32);
find_first_empty(&positions, upper_bound as i32).map(|x| (x, y))
})
.unwrap();
x as usize * 4000000 + y
}
struct Day15;
impl Solution<Input, Output> for Day15 {
fn parse_input<P: AsRef<Path>>(pathname: P) -> Input {
file_to_structs(pathname)
}
fn part_1(input: &Input) -> Output {
cannot_contain(input, 2000000)
}
fn part_2(input: &Input) -> Output {
find_distress(input, 4000000)
}
}
fn main() -> Result<()> {
// Day15::run("sample")
Day15::main()
}
#[cfg(test)]
mod day_15 {
use super::*;
#[test]
fn test_part_1() {
let sample = Day15::parse_input(&format!("samples/{}.txt", Day15::day()));
assert_eq!(cannot_contain(&sample, 10), 26);
}
#[test]
fn test_part_2() {
let sample = Day15::parse_input(&format!("samples/{}.txt", Day15::day()));
assert_eq!(find_distress(&sample, 20), 56000011);
}
}
#[cfg(test)]
mod day_15_unit {
use super::*;
#[test]
fn test_manhattan_basic_example() {
let s = Sensor {
position: Coord::new(8, 7),
beacon: Coord::new(2, 10),
};
assert_eq!(manhattan(&s.position, &s.beacon), 9);
}
#[test]
fn test_manhattan_close() {
let s = Sensor {
position: Coord::new(0, 11),
beacon: Coord::new(2, 10),
};
assert_eq!(manhattan(&s.position, &s.beacon), 3);
}
#[test]
fn test_manhattan_next() {
let s = Sensor {
position: Coord::new(2, 11),
beacon: Coord::new(2, 10),
};
assert_eq!(manhattan(&s.position, &s.beacon), 1);
}
#[test]
fn bounds_at_y_basic_example() {
let s = Sensor {
position: Coord::new(8, 7),
beacon: Coord::new(2, 10),
};
let bounds = s.bounds_at_y(10);
assert!(bounds.is_some());
let (left, right) = bounds.unwrap();
assert_eq!(left, Coord::new(2, 10));
assert_eq!(right, Coord::new(14, 10));
}
#[test]
fn bounds_at_y_none() {
let s = Sensor {
position: Coord::new(8, 7),
beacon: Coord::new(2, 10),
};
let bounds = s.bounds_at_y(20);
assert!(bounds.is_none());
}
#[test]
fn bounds_at_y_close_but_not_same() {
let s = Sensor {
position: Coord::new(8, 7),
beacon: Coord::new(9, 7),
};
let bounds = s.bounds_at_y(7);
assert!(bounds.is_some());
let (left, right) = bounds.unwrap();
assert_eq!(left, Coord::new(7, 7));
assert_eq!(right, Coord::new(9, 7));
}
#[test]
fn bounds_at_y_close_and_same() {
let s = Sensor {
position: Coord::new(8, 7),
beacon: Coord::new(9, 7),
};
let bounds = s.bounds_at_y(6);
assert!(bounds.is_some());
let (left, right) = bounds.unwrap();
assert_eq!(left, Coord::new(8, 6));
assert_eq!(right, Coord::new(8, 6));
}
}