1use utils::geometry::Vec2;
2use utils::prelude::*;
3
4#[derive(Clone, Debug)]
6pub struct Day06 {
7    locations: Vec<Vec2<usize>>,
8    region_threshold: u32,
9}
10
11const WIDTH: usize = 320;
12
13impl Day06 {
14    pub fn new(input: &str, input_type: InputType) -> Result<Self, InputError> {
15        let locations = parser::number_range(0..=999)
16            .repeat_n(", ")
17            .map(Vec2::from)
18            .parse_lines(input)?;
19
20        if locations.len() >= u8::MAX as usize {
21            return Err(InputError::new(input, 0, "too many locations"));
22        }
23
24        let min = locations.iter().map(|p| p.x.min(p.y)).min().unwrap();
25        let max = locations.iter().map(|p| p.x.max(p.y)).max().unwrap();
26        if max - min >= WIDTH {
27            return Err(InputError::new(input, 0, "coordinate range too large"));
28        }
29
30        let min_loc = Vec2::new(min, min);
31        Ok(Self {
32            locations: locations.into_iter().map(|p| p - min_loc).collect(),
33            region_threshold: match input_type {
34                InputType::Example => 32,
35                InputType::Real => 10000,
36            },
37        })
38    }
39
40    #[must_use]
41    pub fn part1(&self) -> u32 {
42        let mut distances = [0u16; WIDTH * WIDTH];
43        let mut closest = [0u8; WIDTH * WIDTH];
44
45        for (i, location) in self.locations.iter().enumerate() {
46            distances[location.y * WIDTH + location.x] = u16::MAX;
48            closest[location.y * WIDTH + location.x] = 1 + i as u8;
49        }
50
51        for y in 0..WIDTH {
53            Self::chunk(
54                &mut distances,
55                &mut closest,
56                (y * WIDTH)..(y * WIDTH) + (WIDTH - 1),
57                1,
58            );
59        }
60        Self::chunk(&mut distances, &mut closest, 0..WIDTH * (WIDTH - 1), WIDTH);
62
63        for y in (0..WIDTH).rev() {
65            Self::chunk(
66                &mut distances,
67                &mut closest,
68                ((y * WIDTH) + 1..(y * WIDTH) + WIDTH).rev(),
69                1usize.wrapping_neg(),
70            );
71        }
72        Self::chunk(
74            &mut distances,
75            &mut closest,
76            (WIDTH..WIDTH * WIDTH).rev(),
77            WIDTH.wrapping_neg(),
78        );
79
80        let mut finite = vec![true; self.locations.len() + 1];
81        finite[0] = false;
82        for i in 0..WIDTH {
83            finite[closest[i] as usize] = false;
84            finite[closest[WIDTH * (WIDTH - 1) + i] as usize] = false;
85            finite[closest[WIDTH * i] as usize] = false;
86            finite[closest[WIDTH * i + WIDTH - 1] as usize] = false;
87        }
88
89        let mut counts = vec![0; self.locations.len() + 1];
90        for &c in closest.iter() {
91            counts[c as usize] += 1;
92        }
93
94        counts
95            .iter()
96            .zip(finite.iter())
97            .filter(|&(_, &i)| i)
98            .map(|(&c, _)| c)
99            .max()
100            .unwrap()
101    }
102
103    #[inline]
104    fn chunk(
105        distances: &mut [u16],
106        closest: &mut [u8],
107        coords: impl Iterator<Item = usize>,
108        offset: usize,
109    ) {
110        for from in coords {
111            let to = from.wrapping_add(offset);
112            let dist = distances[from].saturating_sub(1);
113            if dist > distances[to] {
114                distances[to] = dist;
115                closest[to] = closest[from];
116            } else if dist == distances[to] && closest[to] != closest[from] {
117                closest[to] = 0;
118            }
119        }
120    }
121
122    #[must_use]
123    pub fn part2(&self) -> u32 {
124        let mut x_distances = [0u32; WIDTH];
125        let mut y_distances = [0u32; WIDTH];
126        for location in &self.locations {
127            for i in 0..WIDTH {
128                x_distances[i] += location.x.abs_diff(i) as u32;
129                y_distances[i] += location.y.abs_diff(i) as u32;
130            }
131        }
132
133        let mut region = 0;
134        for y_dist in y_distances {
135            region += x_distances
136                .iter()
137                .map(|&x| x + y_dist)
138                .filter(|&x| x < self.region_threshold)
139                .count() as u32;
140        }
141        region
142    }
143}
144
145examples!(Day06 -> (u32, u32) [
146    {input: "1, 1\n1, 6\n8, 3\n3, 4\n5, 5\n8, 9", part1: 17, part2: 16},
147]);