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
//! # [Day 5: A Maze of Twisty Trampolines, All Alike](http://adventofcode.com/2017/day/5)
//!
//! An urgent <span title="Later, on its turn, it sends you a sorcery.">
//! interrupt</span> arrives from the CPU: it's trapped in a maze of jump
//! instructions, and it would like assistance from any programs with spare
//! cycles to help find the exit.
//!

/// The message includes a list of the offsets for each jump. Jumps are
/// relative: `-1` moves to the previous instruction, and `2` skips the next
/// one. Start at the first instruction in the list. The goal is to follow
/// the jumps until one leads *outside* the list.
pub fn parse_input(input: &str) -> Vec<isize> {
    input
        .split_terminator('\n')
        .map(|x| x.parse::<isize>().expect("Unexpected non-integer jump"))
        .collect::<Vec<_>>()
}

/// In addition, these instructions are a little strange; after each jump,
/// the offset of that instruction increases by `1`. So, if you come across
/// an offset of `3`, you would move three instructions forward, but change
/// it to a `4` for the next time it is encountered.
///
/// For example, consider the following list of jump offsets:
///
/// ```text
/// 0
/// 3
/// 0
/// 1
/// -3
/// ```
///
/// Positive jumps ("forward") move downward; negative jumps move upward.
/// For legibility in this example, these offset values will be written all
/// on one line, with the current instruction marked in parentheses. The
/// following steps would be taken before an exit is found:
///
/// -   `(0) 3  0  1  -3 ` - *before* we have taken any steps.
/// -   `(1) 3  0  1  -3 ` - jump with offset `0` (that is, don't jump at
///     all). Fortunately, the instruction is then incremented to `1`.
/// -   ` 2 (3) 0  1  -3 ` - step forward because of the instruction we just
///     modified. The first instruction is incremented again, now to `2`.
/// -   ` 2  4  0  1 (-3)` - jump all the way to the end; leave a `4`
///     behind.
/// -   ` 2 (4) 0  1  -2 ` - go back to where we just were; increment `-3`
///     to `-2`.
/// -   ` 2  5  0  1  -2 ` - jump `4` steps forward, escaping the maze.
///
/// In this example, the exit is reached in `5` steps.
///
/// ```
/// # use advent_solutions::advent2017::day05::{ parse_input, part1 };
/// # let input = parse_input("0
/// # 3
/// # 0
/// # 1
/// # -3
/// # ");
/// assert_eq!(part1(&input), 5);
/// ```
///
/// *How many steps* does it take to reach the exit?
pub fn part1(jumps: &Vec<isize>) -> usize {
    let jumps = jumps.clone();

    count_steps(jumps, |ip| ip + 1 )
}

/// Now, the jumps are even stranger: after each jump, if the offset was
/// *three or more*, instead *decrease* it by `1`. Otherwise, increase it by
/// `1` as before.
///
/// Using this rule with the above example, the process now takes `10`
/// steps, and the offset values after finding the exit are left as
/// `2 3 2 3 -1`.
///
/// ```
/// # use advent_solutions::advent2017::day05::{ parse_input, part2 };
/// # let input = parse_input("0
/// # 3
/// # 0
/// # 1
/// # -3
/// # ");
/// assert_eq!(part2(&input), 10);
/// ```
///
/// *How many steps* does it now take to reach the exit?
pub fn part2(jumps: &Vec<isize>) -> usize {
    let jumps = jumps.clone();

    count_steps(jumps, |ip| if ip >= 3 { ip - 1 } else { ip + 1 } )
}

/// Counts the steps required to exit the maze, given a instruction mutation
/// function `mut_fn`.
pub fn count_steps<F: Fn(isize) -> isize>(mut memory: Vec<isize>, mut_fn: F) -> usize {
    let mut ip = 0;

    let length = memory.len();

    ::itertools::repeat_call(|| {
        let old_ip = ip;
        ip = (ip as isize + memory[ip]) as usize;
        memory[old_ip] = mut_fn(memory[old_ip]);
        ip
    })
    .take_while(|&ip| ip < length)
    .count()
    + 1
}

test_day!("05", 360603, 25347697);