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
//! # Day 6: Memory Reallocation
//!
//! A debugger program here is having an issue: it is trying to repair a memory reallocation
//! routine, but it keeps getting stuck in an infinite loop.
//!
//! In this area, there are sixteen memory banks; each memory bank can hold any number of
//! **blocks**. The goal of the reallocation routine is to balance the blocks between the memory
//! banks.
//!
//! The reallocation routine operates in cycles. In each cycle, it finds the memory bank with the
//! most blocks (ties won by the lowest-numbered memory bank) and redistributes those blocks among
//! the banks. To do this, it removes all of the blocks from the selected bank, then moves to the
//! next (by index) memory bank and inserts one of the blocks. It continues doing this until it runs
//! out of blocks; if it reaches the last memory bank, it wraps around to the first one.
//!
//! The debugger would like to know how many redistributions can be done before a blocks-in-banks
//! configuration is produced that **has been seen before**.
//!
//! For example, imagine a scenario with only four memory banks:
//!
//! - The banks start with `0`, `2`, `7`, and `0` blocks. The third bank has the most blocks, so it
//!   is chosen for redistribution.
//! - Starting with the next bank (the fourth bank) and then continuing to the first bank, the
//!   second bank, and so on, the `7` blocks are spread out over the memory banks. The fourth,
//!   first, and second banks get two blocks each, and the third bank gets one back. The final
//!   result looks like this: `2 4 1 2`.
//! - Next, the second bank is chosen because it contains the most blocks (four). Because there are
//!   four memory banks, each gets one block. The result is: `3 1 2 3`.
//! - Now, there is a tie between the first and fourth memory banks, both of which have three
//!   blocks. The first bank wins the tie, and its three blocks are distributed evenly over the
//!   other three banks, leaving it with none: `0 2 3 4`.
//! - The fourth bank is chosen, and its four blocks are distributed such that each of the four
//!   banks receives one: `1 3 4 1`.
//! - The third bank is chosen, and the same thing happens: `2 4 1 2`.
//!
//! At this point, we've reached a state we've seen before: `2 4 1 2` was already seen. The infinite
//! loop is detected after the fifth block redistribution cycle, and so the answer in this example
//! is `5`.
//!
//! Given the initial block counts in your puzzle input, **how many redistribution cycles** must be
//! completed before a configuration is produced that has been seen before?
//!
//! ## Part Two
//!
//! Out of curiosity, the debugger would also like to know the size of the loop: starting from a
//! state that has already been seen, how many block redistribution cycles must be performed before
//! that same state is seen again?
//!
//! In the example above, `2 4 1 2` is seen again after four cycles, and so the answer in that
//! example would be `4`.
//!
//! **How many cycles** are in the infinite loop that arises from the configuration in your puzzle
//! input?

use std::collections::HashMap;

use anyhow::{ensure, Context, Result};
use itertools::Itertools;

pub const INPUT: &str = include_str!("d06.txt");

pub fn solve_part_one(input: &str) -> Result<u32> {
    Ok(solve(parse_input(input)?).0)
}

pub fn solve_part_two(input: &str) -> Result<u32> {
    Ok(solve(parse_input(input)?).1)
}

fn parse_input(input: &str) -> Result<Vec<u32>> {
    input
        .lines()
        .next()
        .context("expected one line of input")?
        .split_whitespace()
        .map(|v| v.parse().map_err(Into::into))
        .collect()
}

fn find_max(values: &[u32]) -> (usize, u32) {
    values.iter().cloned().enumerate().fold((0, 0), |max, v| if v.1 > max.1 { v } else { max })
}

fn solve(mut input: Vec<u32>) -> (u32, u32) {
    let input_len = input.len();

    let mut history = HashMap::new();
    let mut count = 0;

    history.insert(input.clone(), 0);

    loop {
        let (mut i, mut max) = find_max(&input);

        count += 1;
        input[i] = 0;

        while max > 0 {
            i += 1;
            input[i % input_len] += 1;
            max -= 1;
        }

        if let Some(c) = history.get(&input) {
            return (count, count - *c);
        }

        history.insert(input.clone(), count);
    }
}

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

    #[test]
    fn part_one() {
        assert_eq!(5, solve_part_one("0\t2\t7\t0").unwrap());
    }

    #[test]
    fn part_two() {
        assert_eq!(4, solve_part_two("0\t2\t7\t0").unwrap());
    }
}