107 lines
2.4 KiB
Rust
107 lines
2.4 KiB
Rust
|
use itertools::Itertools;
|
||
|
|
||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||
|
pub struct Position {
|
||
|
pub x: usize,
|
||
|
pub y: usize,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||
|
pub struct Field {
|
||
|
rows: usize,
|
||
|
columns: usize,
|
||
|
astroids: Vec<bool>,
|
||
|
}
|
||
|
|
||
|
fn gcd(mut x: isize, mut y: isize) -> isize {
|
||
|
loop {
|
||
|
if y == 0 { return x; }
|
||
|
x = x % y;
|
||
|
if x == 0 { return y; }
|
||
|
y = y % x;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Field {
|
||
|
pub fn parse(input: &str) -> Self {
|
||
|
let lines = input.lines().collect::<Vec<_>>();
|
||
|
let rows = lines.len();
|
||
|
let columns = lines[0].len();
|
||
|
let mut astroids = Vec::new();
|
||
|
for line in lines {
|
||
|
assert_eq!(columns, line.len());
|
||
|
for cell in line.chars() {
|
||
|
astroids.push(match cell {
|
||
|
'#' => true,
|
||
|
'.' => false,
|
||
|
_ => panic!("Unexpected cell: {:?}", cell),
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Self {
|
||
|
rows,
|
||
|
columns,
|
||
|
astroids,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn sees(&self, a: Position, b: Position) -> bool {
|
||
|
if a == b { return true; }
|
||
|
let diff_x = b.x as isize - a.x as isize;
|
||
|
let diff_y = b.y as isize - a.y as isize;
|
||
|
let d = gcd(diff_x, diff_y).abs();
|
||
|
let diff_x = diff_x / d;
|
||
|
let diff_y = diff_y / d;
|
||
|
let mut pos = a;
|
||
|
loop {
|
||
|
pos.x = (pos.x as isize + diff_x) as usize;
|
||
|
pos.y = (pos.y as isize + diff_y) as usize;
|
||
|
if pos == b { return true; }
|
||
|
if self[pos] { return false; }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn all_astroids(&self) -> impl Iterator<Item = Position> + '_ {
|
||
|
(0..self.columns).cartesian_product(0..self.rows).map(|(x, y)| Position { x, y }).filter(move |pos| self[*pos])
|
||
|
}
|
||
|
|
||
|
pub fn best_monitoring_station(&self) -> (Position, usize) {
|
||
|
self.all_astroids().map(|monitor| {
|
||
|
// can see itself, but only count "others"
|
||
|
(monitor, self.all_astroids().filter(|astroid| self.sees(monitor, *astroid)).count() - 1)
|
||
|
}).max_by_key(|(_, n)| *n).unwrap()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl std::ops::Index<Position> for Field {
|
||
|
type Output = bool;
|
||
|
|
||
|
fn index(&self, index: Position) -> &Self::Output {
|
||
|
assert!(index.x < self.columns);
|
||
|
assert!(index.y < self.rows);
|
||
|
&self.astroids[index.y * self.columns + index.x]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn main() {
|
||
|
let input = include_str!("input.txt");
|
||
|
println!("Best monitoring station sees astroids: {}", Field::parse(input).best_monitoring_station().1);
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod day10test {
|
||
|
use super::*;
|
||
|
|
||
|
#[test]
|
||
|
fn examples1() {
|
||
|
assert_eq!(
|
||
|
Field::parse(".#..#
|
||
|
.....
|
||
|
#####
|
||
|
....#
|
||
|
...##").best_monitoring_station(),
|
||
|
(Position { x: 3, y: 4 }, 8),
|
||
|
);
|
||
|
}
|
||
|
}
|