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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! This is my solution for [Advent of Code - Day 2 - _Dive!_](https://adventofcode.com/2021/day/2)
//!
//! Today involves parsing a sequence of instructions that direct the submarine to move on a grid.
//! First [`parse_line`] is used to parse the string input into an internal representation
//! [`Instruction`]. Rust has powerful pattern matching but requires that it is exhaustive. Front
//! loading parsing the strings both makes the code more understandable, but also simplifies the
//! match statements as we don't need to repeatedly handle possible bad input.
//!
//! The two parts differ in how the input should be interpreted. I've implemented both as a fold
//! over the sequence of instructions, matching the Direction and updating the position as specified
//! for that part. Part one takes the instructions at face value, the logic is implemented by
//! [`navigate`]. Part two tracks a third variable 'aim', but is otherwise very similar. The logic
//! is implemented by [`navigate_and_aim`].

use crate::day_2::Direction::{DOWN, FORWARD, UP};
use std::fs;

/// There are three direction strings expected in the input. Parsing those into an Enum type helps
/// doing exhaustive matches later
#[derive(Eq, PartialEq, Debug)]
enum Direction {
    FORWARD,
    UP,
    DOWN,
}

/// Each line of the input is a pair of direction and magnitude - alias this for clarity
type Instruction = (Direction, isize);

/// The entry point for running the solutions with the 'real' puzzle input.
///
/// - The puzzle input is expected to be at `<project_root>/res/day-2-input`
/// - It is expected this will be called by [`super::main()`] when the user elects to run day 2
///
/// This also handles converting the raw input into a `Vec<Instruction>`, delegating the parsing to
/// [`parse_line`].
pub fn run() {
    let contents: Vec<Instruction> = fs::read_to_string("res/day-2-input")
        .expect("Failed to read file")
        .lines()
        .map(|line| parse_line(line))
        .collect();

    let (h1, d1) = navigate(&contents);
    println!("Final position ({}, {}) = {}", h1, d1, h1 * d1);

    let (h2, d2, _) = navigate_and_aim(&contents);
    println!("Final position with aiming ({}, {}) = {}", h2, d2, h2 * d2)
}

/// Parses a line in the format `(forward|up|down) \d+` into the internal representation
/// [`Instruction`]. Will panic if the provided line does not match the expected format.
///
/// # Example from puzzle specification
/// ```rust
/// assert_eq!(parse_line("forward 5"), (FORWARD, 5));
/// assert_eq!(parse_line("down 5"),    (DOWN,    5));
/// assert_eq!(parse_line("forward 8"), (FORWARD, 8));
/// assert_eq!(parse_line("up 3"),      (UP,      3));
/// assert_eq!(parse_line("down 8"),    (DOWN,    8));
/// assert_eq!(parse_line("forward 2"), (FORWARD, 2));
/// ```
fn parse_line(line: &str) -> Instruction {
    if let Some((direction, magnitude)) = line.split_once(" ") {
        return (
            match direction {
                "forward" => FORWARD,
                "up" => UP,
                "down" => DOWN,
                unexpected => panic!("Unexpected direction {}", unexpected),
            },
            magnitude
                .parse::<isize>()
                .expect("Magnitude was not a number"),
        );
    }

    panic!("Line '{}' was not in the expected format", line)
}

/// This starts with the submarine at the origin, and moves using the following rules:
/// - _Forward_: Increase the horizontal position by the magnitude
/// - _Up_: Decrease the depth by the magnitude
/// - _Down_: Increase the depth by the magnitude
///
/// The final position after applying all the instructions in order is returned as a tuple
/// `(horizontal_position, depth)`
/// # Example from puzzle specification
/// ```rust
/// let input = vec![
///     (FORWARD, 5),
///     (DOWN, 5),
///     (FORWARD, 8),
///     (UP, 3),
///     (DOWN, 8),
///     (FORWARD, 2),
/// ]
/// assert_eq!(navigate(&input), (15, 10))
/// ```
fn navigate(instructions: &Vec<Instruction>) -> (isize, isize) {
    instructions.iter().fold(
        (0, 0),
        |(horizontal, depth), (direction, magnitude)| match direction {
            FORWARD => (horizontal + magnitude, depth),
            UP => (horizontal, depth - magnitude),
            DOWN => (horizontal, depth + magnitude),
        },
    )
}

/// This starts with the submarine at the origin, with a third variable 'aim' also set to 0. The
/// position and aim are updated using the following rules:
/// - _Forward_: Increase the horizontal position by the magnitude, increase the depth by
///   `(magnitude x current aim)`
/// - _Up_: Decrease the aim by the magnitude
/// - _Down_: Increase the aim by the magnitude
///
/// The final position and aim after applying all the instructions in order is returned as a tuple
/// `(horizontal_position, depth, aim)`
/// # Example from puzzle specification
/// ```rust
/// let input = vec![
///     (FORWARD, 5),
///     (DOWN, 5),
///     (FORWARD, 8),
///     (UP, 3),
///     (DOWN, 8),
///     (FORWARD, 2),
/// ]
/// assert_eq!(navigate_and_aim(&input), (15, 60, 10))
/// ```
fn navigate_and_aim(instructions: &Vec<Instruction>) -> (isize, isize, isize) {
    instructions.iter().fold(
        (0, 0, 0),
        |(horizontal, depth, aim), (direction, magnitude)| match direction {
            FORWARD => (horizontal + magnitude, depth + (aim * magnitude), aim),
            UP => (horizontal, depth, aim - magnitude),
            DOWN => (horizontal, depth, aim + magnitude),
        },
    )
}

#[cfg(test)]
mod tests {
    use crate::day_2::Direction::*;
    use crate::day_2::{navigate, navigate_and_aim, parse_line, Instruction};

    #[test]
    fn can_parse() {
        assert_eq!(parse_line("forward 5"), (FORWARD, 5));
        assert_eq!(parse_line("down 5"), (DOWN, 5));
        assert_eq!(parse_line("forward 8"), (FORWARD, 8));
        assert_eq!(parse_line("up 3"), (UP, 3));
        assert_eq!(parse_line("down 8"), (DOWN, 8));
        assert_eq!(parse_line("forward 2"), (FORWARD, 2));
    }

    #[test]
    fn can_navigate() {
        assert_eq!(navigate(&test_data()), (15, 10))
    }

    #[test]
    fn can_navigate_and_aim() {
        assert_eq!(navigate_and_aim(&test_data()), (15, 60, 10))
    }

    fn test_data() -> Vec<Instruction> {
        vec![
            (FORWARD, 5),
            (DOWN, 5),
            (FORWARD, 8),
            (UP, 3),
            (DOWN, 8),
            (FORWARD, 2),
        ]
    }
}