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
/* Copyright 2022 Mario Finelli
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Advent of Code 2015 Day 2: <https://adventofcode.com/2015/day/2>
//!
//! The solutions today are simple and just involve parsing numbers and
//! comparing them amongst each other. Originally this worked with some ugly
//! if/else statments and array sorting, but after solving the puzzle I
//! decided to switch it to use a [min
//! heap](https://en.wikipedia.org/wiki/Min-max_heap) which I think results
//! in a much cleaner and easier to understand/reason about solution.

use std::cmp::Reverse;
use std::collections::BinaryHeap;

/// The solution for the day two challenge.
///
/// Given the input as a string we start by parsing all of the dimensions on
/// each line. Then if we're doing part `1` we calculate the area of each
/// side, add them to our heap and then pop the smallest value back. The
/// actual calculation comes directly from the challenge prompt:
/// `2*l*w + 2*w*h + 2*h*l` plus the extra (area of the smallest side that we
/// just got).
///
/// If we're doing part `2` then we calculate the cubic volume (`l * w * h`),
/// and then find the smallest two sides by adding the dimensions to the heap
/// and then popping the smallest two values back. The calculation again
/// comes directly from the prompt and is equal to the diameter of the
/// smallest side (two times the smallest two dimensions each) plus the volume
/// previously calculated.
///
/// # Example
/// ```rust
/// # use aoc::y15d02::y15d02;
/// let input = "1x2x3\n4x5x6\n"; // probably read this from the input file...
/// assert_eq!(y15d02(input, 1), 192);
/// assert_eq!(y15d02(input, 2), 150);
/// ```
///
/// # Min-Heap values
/// The rust documentation isn't exactly clear about how to work with a
/// min-heap which uses the same data structure as the max heap, but this
/// helpful answer on [stack overflow](https://stackoverflow.com/a/71121846)
/// provides the answer, reproduced here:
///
/// ```rust
/// # use std::cmp::Reverse;
/// let reversed = Reverse(42);
/// assert_eq!(reversed.0, 42);
/// // alternatively, using destructuring:
/// let Reverse(n) = reversed;
/// assert_eq!(n, 42);
/// ```
pub fn y15d02(input: &str, part: u32) -> u32 {
    let lines: Vec<_> = input.lines().collect();
    let mut total = 0;

    for line in lines {
        let dimensions: Vec<&str> = line.split('x').collect();
        let l: u32 = dimensions[0].parse().unwrap();
        let w: u32 = dimensions[1].parse().unwrap();
        let h: u32 = dimensions[2].parse().unwrap();

        if part == 1 {
            let lw = l * w;
            let wh = w * h;
            let hl = h * l;

            let mut heap =
                BinaryHeap::from([Reverse(lw), Reverse(wh), Reverse(hl)]);
            let Reverse(extra) = heap.pop().unwrap();

            total += 2 * lw + 2 * wh + 2 * hl + extra;
        } else {
            let volume = l * w * h;
            let mut heap =
                BinaryHeap::from([Reverse(l), Reverse(w), Reverse(h)]);

            let Reverse(s1) = heap.pop().unwrap();
            let Reverse(s2) = heap.pop().unwrap();

            total += 2 * s1 + 2 * s2 + volume;
        }
    }

    total
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    #[test]
    fn it_works() {
        let mut input = "2x3x4\n";
        assert_eq!(y15d02(input, 1), 58);
        assert_eq!(y15d02(input, 2), 34);

        input = "1x1x10";
        assert_eq!(y15d02(input, 1), 43);
        assert_eq!(y15d02(input, 2), 14);

        input = "2x3x4\n1x1x10\n";
        assert_eq!(y15d02(input, 1), 101);
        assert_eq!(y15d02(input, 2), 48);
    }

    #[test]
    fn the_solution() {
        let contents = fs::read_to_string("input/2015/day02.txt").unwrap();

        assert_eq!(y15d02(&contents, 1), 1606483);
        assert_eq!(y15d02(&contents, 2), 3842356);
    }
}