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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! # Crossed Wires
//!
//! The input follow some implicit rules that can be used to simplify our approach:
//!
//! * Wires cross only at right angles to each other, so we only need to consider horizontal lines
//!   when moving vertically and vice-versa.
//! * There is only a single vertical line at a given x coordinates and vice-versa.
//!
//! This makes [`BTreeMap`] a great choice to store horizontal or vertical line segments as there
//! are no collisions. The [`range`] method can lookup all line segments contained between two
//! coordinates to check for intersections.
//!
//! First we build two maps, one vertical and one horizontal, of each line segment for the first
//! wire. Then we trace the steps of the second wire, looking for any intersections. We calculate
//! both part one and part two at the same time, by also including the distance so far
//! from the starting point of each lines.
//!
//! [`range`]: BTreeMap::range
use crate::util::parse::*;
use crate::util::point::*;
use std::collections::BTreeMap;

type Input = (i32, i32);

struct Line {
    start: Point,
    end: Point,
    distance: i32,
}

pub fn parse(input: &str) -> Input {
    // Map a line into an iterator of direction and distance pairs.
    let lines: Vec<_> = input.lines().collect();
    let steps = |i: usize| {
        let first = lines[i].bytes().filter(u8::is_ascii_alphabetic);
        let second = lines[i].iter_signed::<i32>();
        first.zip(second)
    };

    // Build two maps, one for vertical segments and one for horizontal.
    let mut start = ORIGIN;
    let mut distance = 0;
    let mut vertical = BTreeMap::new();
    let mut horizontal = BTreeMap::new();

    for (direction, amount) in steps(0) {
        let delta = Point::from(direction);
        let end = start + delta * amount;
        let line = Line { start, end, distance };

        if start.x == end.x {
            vertical.insert(start.x, line);
        } else {
            horizontal.insert(start.y, line);
        }

        start = end;
        distance += amount;
    }

    // Trace the steps of the second wire, checking for intersections.
    let mut start = ORIGIN;
    let mut distance = 0;
    let mut manhattan = i32::MAX;
    let mut delay = i32::MAX;

    for (direction, amount) in steps(1) {
        let delta = Point::from(direction);
        let end = start + delta * amount;

        // Use a block to scope the `update` lamdbas mutable borrow of `distance`.
        {
            // Checks for intersections, ignoring the initial intersection at the origin.
            let mut update = |line: &Line, candidate: Point| {
                if candidate.manhattan(line.start) < line.end.manhattan(line.start)
                    && candidate.signum(line.start) == line.end.signum(line.start)
                    && candidate.manhattan(ORIGIN) > 0
                {
                    manhattan = manhattan.min(candidate.manhattan(ORIGIN));
                    delay = delay.min(
                        distance
                            + candidate.manhattan(start)
                            + line.distance
                            + candidate.manhattan(line.start),
                    );
                }
            };

            // BTreeMaps are sorted and can return all key/value pairs in a range.
            match direction {
                b'U' => {
                    for (&y, line) in horizontal.range(end.y..=start.y) {
                        update(line, Point::new(start.x, y));
                    }
                }
                b'D' => {
                    for (&y, line) in horizontal.range(start.y..=end.y) {
                        update(line, Point::new(start.x, y));
                    }
                }
                b'L' => {
                    for (&x, line) in vertical.range(end.x..=start.x) {
                        update(line, Point::new(x, start.y));
                    }
                }
                b'R' => {
                    for (&x, line) in vertical.range(start.x..=end.x) {
                        update(line, Point::new(x, start.y));
                    }
                }
                _ => unreachable!(),
            }
        }

        start = end;
        distance += amount;
    }

    (manhattan, delay)
}

pub fn part1(input: &Input) -> i32 {
    input.0
}

pub fn part2(input: &Input) -> i32 {
    input.1
}