1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use std::collections::HashSet;
use utils::point::Point2D;
use utils::prelude::*;

/// Counting unique points.
#[derive(Clone, Debug)]
pub struct Day03 {
    directions: Vec<Point2D<i32>>,
}

impl Day03 {
    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
        Ok(Self {
            directions: input
                .chars()
                .map(|c| match c {
                    '^' => Ok(Point2D::UP),
                    '>' => Ok(Point2D::RIGHT),
                    'v' => Ok(Point2D::DOWN),
                    '<' => Ok(Point2D::LEFT),
                    _ => Err(InputError::new(input, c, "expected one of ^>v<")),
                })
                .collect::<Result<Vec<Point2D<i32>>, InputError>>()?,
        })
    }

    #[must_use]
    pub fn part1(&self) -> usize {
        let mut pos = Point2D::default();

        self.count_positions(|dir| {
            pos += dir;
            pos
        })
    }

    #[must_use]
    pub fn part2(&self) -> usize {
        let (mut pos1, mut pos2) = Default::default();

        self.count_positions(|dir| {
            (pos1, pos2) = (pos2, pos1);
            pos1 += dir;
            pos1
        })
    }

    fn count_positions(&self, mut f: impl FnMut(Point2D<i32>) -> Point2D<i32>) -> usize {
        let mut set = HashSet::with_capacity(self.directions.len());
        set.insert(Point2D::default());

        for &dir in &self.directions {
            set.insert(f(dir));
        }

        set.len()
    }
}

examples!(Day03 -> (usize, usize) [
    {input: ">", part1: 2},
    {input: "^>", part2: 3},
    {input: "^>v<", part1: 4, part2: 3},
    {input: "^v^v^v^v^v", part1: 2, part2: 11},
]);