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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! # Spiral Memory
//!
//! ## Part One
//!
//! Consider the layout as a sequence of hollow donuts. We find the donut that contains the value
//! which gives one component of the Manhattan value. The second component is the distance from
//! the center of each edge.
//!
//! For example say the target value is 20. We find the donut then subtract the inner donuts
//! to make the values relative then calculate the values modulo the edge size.
//!
//! ```none
//!                                                      <------------
//!     17  16  15  14  13      7   6   5   4   3        3   2  [1]  0   3  ^
//!     18   5   4   3  12      8               2     |  0               2  |
//!     19   6   1   2  11 =>   9               1 =>  | [1]             [1] |
//!     20   7   8   9  10     10               0     |  2               0  |
//!     21  22  23  24  25     11  12  13  14  15     v  3   0  [1]  2   3
//!                                                          ------------>
//! ```
//!
//! The first component is the horizonal or vertical distance from the centre to the ring,
//! in this case 2 steps. The second component is the distance from the target number to the
//! center of each edge, in this case 2 - 1 = 1.
//!
//! ## Part Two
//!
//! We use the [`Point`] utility to move in the spiral direction. Values are stored in a hashmap
//! defaulting to zero if the value doesn't exist yet.
use crate::util::hash::*;
use crate::util::parse::*;
use crate::util::point::*;

pub fn parse(input: &str) -> u32 {
    input.unsigned()
}

pub fn part1(input: &u32) -> u32 {
    let target = *input;
    let mut a = 3;

    // Find the donut that contains the value.
    while a * a < target {
        a += 2;
    }
    let b = a - 1;
    let c = a - 2;

    // Distance to donut plus distance to center of edge.
    (b / 2) + (c / 2).abs_diff((target - c * c - 1) % b)
}

pub fn part2(input: &u32) -> u32 {
    let target = *input;
    let mut size = 2;

    let mut position = Point::new(1, 0);
    let mut direction = UP;
    let mut left = LEFT;

    let mut values = FastMap::build([(ORIGIN, 1)]);

    'outer: loop {
        // Fill in one donut at a time.
        for edge in 0..4 {
            for i in 0..size {
                // Default to zero if a value doesn't exist yet.
                let value = |point| values.get(&point).unwrap_or(&0);

                // Values in front and to the right (relative to our current direction) are not
                // filled in yet, so we only need to consider values to the left and behind.
                let next = value(position - direction)
                    + value(position + left + direction)
                    + value(position + left)
                    + value(position + left - direction);

                if next > target {
                    break 'outer next;
                }
                values.insert(position, next);

                // Turn left at the very end of each edge, unless this is the last edge of
                // the square.
                if i == size - 1 && edge < 3 {
                    position += left;
                } else {
                    position += direction;
                }
            }

            direction = left;
            left = left.counter_clockwise();
        }

        size += 2;
    }
}