|
| 1 | +//! # Spiral Memory |
| 2 | +//! |
| 3 | +//! ## Part One |
| 4 | +//! |
| 5 | +//! Consider the layout as a sequence of hollow donuts. We find the donut that contains the value |
| 6 | +//! which gives one component of the Manhattan value. The second component is the distance from |
| 7 | +//! the center of each edge. |
| 8 | +//! |
| 9 | +//! For example say the target value is 20. We find the donut then subtract the inner donuts |
| 10 | +//! to make the values relative then calculate the values modulo the edge size. |
| 11 | +//! |
| 12 | +//! ```none |
| 13 | +//! <------------ |
| 14 | +//! 17 16 15 14 13 7 6 5 4 3 3 2 [1] 0 3 ^ |
| 15 | +//! 18 5 4 3 12 8 2 | 0 2 | |
| 16 | +//! 19 6 1 2 11 => 9 1 => | [1] [1] | |
| 17 | +//! 20 7 8 9 10 10 0 | 2 0 | |
| 18 | +//! 21 22 23 24 25 11 12 13 14 15 v 3 0 [1] 2 3 |
| 19 | +//! ------------> |
| 20 | +//! ``` |
| 21 | +//! |
| 22 | +//! The first component is the horizonal or vertical distance from the centre to the ring, |
| 23 | +//! in this case 2 steps. The second component is the distance from the target number to the |
| 24 | +//! center of each edge, in this case 2 - 1 = 1. |
| 25 | +//! |
| 26 | +//! ## Part Two |
| 27 | +//! |
| 28 | +//! We use the [`Point`] utility to move in the spiral direction. Values are stored in a hashmap |
| 29 | +//! defaulting to zero if the value doesn't exist yet. |
| 30 | +use crate::util::hash::*; |
| 31 | +use crate::util::parse::*; |
| 32 | +use crate::util::point::*; |
| 33 | + |
| 34 | +pub fn parse(input: &str) -> u32 { |
| 35 | + input.unsigned() |
| 36 | +} |
| 37 | + |
| 38 | +pub fn part1(input: &u32) -> u32 { |
| 39 | + let target = *input; |
| 40 | + let mut a = 3; |
| 41 | + |
| 42 | + // Find the donut that contains the value. |
| 43 | + while a * a < target { |
| 44 | + a += 2; |
| 45 | + } |
| 46 | + let b = a - 1; |
| 47 | + let c = a - 2; |
| 48 | + |
| 49 | + // Distance to donut plus distance to center of edge. |
| 50 | + (b / 2) + (c / 2).abs_diff((target - c * c - 1) % b) |
| 51 | +} |
| 52 | + |
| 53 | +pub fn part2(input: &u32) -> u32 { |
| 54 | + let target = *input; |
| 55 | + let mut size = 2; |
| 56 | + |
| 57 | + let mut position = Point::new(1, 0); |
| 58 | + let mut direction = UP; |
| 59 | + let mut left = LEFT; |
| 60 | + |
| 61 | + let mut values = FastMap::build([(ORIGIN, 1)]); |
| 62 | + |
| 63 | + 'outer: loop { |
| 64 | + // Fill in one donut at a time. |
| 65 | + for edge in 0..4 { |
| 66 | + for i in 0..size { |
| 67 | + // Default to zero if a value doesn't exist yet. |
| 68 | + let value = |point| values.get(&point).unwrap_or(&0); |
| 69 | + |
| 70 | + // Values in front and to the right (relative to our current direction) are not |
| 71 | + // filled in yet, so we only need to consider values to the left and behind. |
| 72 | + let next = value(position - direction) |
| 73 | + + value(position + left + direction) |
| 74 | + + value(position + left) |
| 75 | + + value(position + left - direction); |
| 76 | + |
| 77 | + if next > target { |
| 78 | + break 'outer next; |
| 79 | + } |
| 80 | + values.insert(position, next); |
| 81 | + |
| 82 | + // Turn left at the very end of each edge, unless this is the last edge of |
| 83 | + // the square. |
| 84 | + if i == size - 1 && edge < 3 { |
| 85 | + position += left; |
| 86 | + } else { |
| 87 | + position += direction; |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + direction = left; |
| 92 | + left = left.counter_clockwise(); |
| 93 | + } |
| 94 | + |
| 95 | + size += 2; |
| 96 | + } |
| 97 | +} |
0 commit comments