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
//! # Transparent Origami
//!
//! There are 2 possible approaches to tracking the position of dots after each fold:
//! * A `HashSet` that will collapse duplicate entries
//! * An array of sufficient dimensions to track every possible coordinate.
//!
//! We will use both approaches for speed, the first in part 1 and the second in part 2.
//!
//! For part 2 we can determine the final size of the paper by taking the *last* x and y
//! coordinates from the fold instructions. It's then faster and more convenienent to process
//! each point completely and update the final location, than to step through intermediate folds.
use crate::util::hash::*;
use crate::util::iter::*;
use crate::util::parse::*;
use crate::util::point::*;

#[derive(Clone, Copy)]
pub enum Fold {
    Horizontal(i32),
    Vertical(i32),
}

pub struct Input {
    points: Vec<Point>,
    folds: Vec<Fold>,
}

/// Parse the input into collections of [`Point`] and [`Fold`] structs.
pub fn parse(input: &str) -> Input {
    let (prefix, suffix) = input.split_once("\n\n").unwrap();

    let points: Vec<_> = prefix.iter_signed().chunk::<2>().map(|[x, y]| Point::new(x, y)).collect();

    let folds: Vec<_> = suffix
        .lines()
        .map(|line| match line.split_once('=').unwrap() {
            ("fold along x", x) => Fold::Horizontal(x.signed()),
            ("fold along y", y) => Fold::Vertical(y.signed()),
            _ => unreachable!(),
        })
        .collect();

    Input { points, folds }
}

/// Fold once then count dots. The sample data folds along `y` and my input folded along `x`
/// testing both possibilities.
pub fn part1(input: &Input) -> usize {
    match input.folds[0] {
        Fold::Horizontal(x) => {
            input.points.iter().map(|&p| fold_horizontal(x, p)).collect::<FastSet<_>>().len()
        }
        Fold::Vertical(y) => {
            input.points.iter().map(|&p| fold_vertical(y, p)).collect::<FastSet<_>>().len()
        }
    }
}

/// Decode secret message.
///
/// The output is a multi-line string to allow integration testing. The final dimensions of the
/// paper are found from the last `x` and `y` fold coordinates.
pub fn part2(input: &Input) -> String {
    let mut width = 0;
    let mut height = 0;

    for &fold in &input.folds {
        match fold {
            Fold::Horizontal(x) => width = x,
            Fold::Vertical(y) => height = y,
        }
    }

    let mut grid = vec![false; (width * height) as usize];

    for point in &input.points {
        let mut point = *point;

        for &fold in &input.folds {
            point = match fold {
                Fold::Horizontal(x) => fold_horizontal(x, point),
                Fold::Vertical(y) => fold_vertical(y, point),
            }
        }

        grid[(point.y * width + point.x) as usize] = true;
    }

    let mut code = String::new();
    for y in 0..height {
        code.push('\n');
        for x in 0..width {
            let c = if grid[(y * width + x) as usize] { '#' } else { '.' };
            code.push(c);
        }
    }
    code
}

/// Fold point at `x` coordinate, doing nothing if the point is to the left of the fold line.
#[inline]
fn fold_horizontal(x: i32, p: Point) -> Point {
    if p.x < x {
        p
    } else {
        Point::new(2 * x - p.x, p.y)
    }
}

/// Fold point at `y` coordinate, doing nothing if the point is above the fold line.
#[inline]
fn fold_vertical(y: i32, p: Point) -> Point {
    if p.y < y {
        p
    } else {
        Point::new(p.x, 2 * y - p.y)
    }
}