advent_of_code_2024/
day_1.rs

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
//! This is my solution for [Advent of Code - Day 1: _Historian Hysteria_](https://adventofcode.com/2024/day/1)
//!
//! The input has been turned into a list of u32 for each column by [`parse_input`]. Part 1 is solved in two steps
//! [`to_sorted_pairs`] sorts the lists and zips them together, then [`sum_diffs`] reduces the list of pairs to the
//! puzzle solution. Part 2 is solved by [`sum_similarity_scores`].

use itertools::Itertools;
use std::fs;

/// The entry point for running the solutions with the 'real' puzzle input.
///
/// - The puzzle input is expected to be at `<project_root>/res/day-1-input`
/// - It is expected this will be called by [`super::main()`] when the user elects to run day 1.
pub fn run() {
    let contents = fs::read_to_string("res/day-1-input.txt").expect("Failed to read file");
    let (left, right) = parse_input(&contents);

    println!(
        "Sum of distances: {}",
        sum_diffs(&to_sorted_pairs(&left, &right))
    );
    println!(
        "Sum of similarity scores: {}",
        sum_similarity_scores(&left, &right)
    );
}

/// Build up lists of ids from the puzzle input. The input is two columns of numbers separated by three spaces, e.g.
///
/// ```text
/// 3   4
/// 4   3
/// 2   5
/// 1   3
/// 3   9
/// 3   3
/// ```
fn parse_input(input: &String) -> (Vec<u32>, Vec<u32>) {
    let mut left = vec![];
    let mut right = vec![];

    input
        .lines()
        .flat_map(|line| line.split_once("   "))
        .for_each(|(l, r)| {
            left.push(l.parse::<u32>().unwrap());
            right.push(r.parse::<u32>().unwrap());
        });

    (left, right)
}

/// The first part of the solution to part 1. Pair the lowest integers in each list, then second lowest, and so on...
fn to_sorted_pairs(left: &Vec<u32>, right: &Vec<u32>) -> Vec<(u32, u32)> {
    let sorted_left = left.iter().cloned().sorted();
    let sorted_right = right.iter().cloned().sorted();
    sorted_left.zip(sorted_right).collect()
}

/// The second part of the solution to part 1, given a sorted list of pairs, sum the distance between them
fn sum_diffs(pairs: &Vec<(u32, u32)>) -> u32 {
    pairs.iter().map(|&(l, r)| l.abs_diff(r)).sum()
}

/// The solution to part 2. The similarity score for a number in the left-hand column is that number multiplied by the
/// number of times it appears in the right-hand column.
fn sum_similarity_scores(left: &Vec<u32>, right: &Vec<u32>) -> usize {
    let lookup = right.iter().counts();
    left.iter()
        .map(|&id| (id as usize) * lookup.get(&id).unwrap_or(&0usize))
        .sum()
}

#[cfg(test)]
mod tests {
    use crate::day_1::*;
    
    fn sample_input() -> String {
        "3   4
4   3
2   5
1   3
3   9
3   3"
            .to_string()
    }
    #[test]
    fn can_parse_input() {
        assert_eq!(
            parse_input(&sample_input()),
            (vec![3, 4, 2, 1, 3, 3], vec![4, 3, 5, 3, 9, 3])
        );
    }

    #[test]
    fn can_generate_pairs() {
        assert_eq!(
            to_sorted_pairs(&vec![3, 4, 2, 1, 3, 3], &vec![4, 3, 5, 3, 9, 3]),
            vec!((1, 3), (2, 3), (3, 3), (3, 4), (3, 5), (4, 9))
        );
    }

    #[test]
    fn can_sum_diff() {
        assert_eq!(
            sum_diffs(&vec!((1, 3), (2, 3), (3, 3), (3, 4), (3, 5), (4, 9))),
            11
        );
    }

    #[test]
    fn can_sum_similarity_scores() {
        assert_eq!(
            sum_similarity_scores(&vec![3, 4, 2, 1, 3, 3], &vec![4, 3, 5, 3, 9, 3]),
            31
        )
    }
}