day(15): add solution
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
f7840f5eae
commit
c087e388ac
4 changed files with 401 additions and 0 deletions
106
Cargo.lock
generated
106
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
14
samples/day15.txt
Normal 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
279
src/bin/day15.rs
Normal 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));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue