aoc/year2017/
day18.rs

1//! # Duet
2//!
3//! Reverse engineering the code shows that the program is broken into 2 sections.
4//! Each input differs only in the number specified on line 10.
5//!
6//! The first section is only executed by program 0 and generates a pseudorandom sequence of
7//! 127 numbers between 0 and 9999. The programs then take turns implementing the innner loop of
8//! the [imfamous bubble sort](https://en.wikipedia.org/wiki/Bubble_sort) in descending order.
9//!
10//! The partially sorted sequence is passed back and forth between each program until in final
11//! sorted order. Assembly code annotated with Rust pseduocode:
12//!
13//! ```none
14//!     set i 31
15//!     set a 1
16//!     mul p 17
17//!     jgz p p         if p == 0 {
18//!     mul a 2             let a = 2 ^ 31 - 1 = 0x7fffffff;
19//!     add i -1
20//!     jgz i -2
21//!     add a -1
22//!     set i 127
23//!     set p SEED          let mut p = SEED;
24//!     mul p 8505          for _ in 0..127 {
25//!     mod p a                 p = (p * 8505) % a;
26//!     mul p 129749
27//!     add p 12345
28//!     mod p a                 p = (p * 129749 + 12345) % a;
29//!     set b p
30//!     mod b 10000
31//!     snd b                   send(p % 10000)
32//!     add i -1
33//!     jgz i -9            }
34//!     jgz a 3         }
35//!     rcv b           // These two lines deadlock the program
36//!     jgz b -1        // once the list is sorted.
37//!     set f 0         while swapped { // f is 0 when list is sorted
38//!     set i 126           a = receive();
39//!     rcv a               for _ in 0..126 {
40//!     rcv b                   b = receive();
41//!     set p a
42//!     mul p -1
43//!     add p b
44//!     jgz p 4                 if b <= a {
45//!     snd a                       send(a);
46//!     set a b                     a = b;
47//!     jgz 1 3                 } else {
48//!     snd b                       send(b);
49//!     set f 1                     swapped = true;
50//!     add i -1                }
51//!     jgz i -11           }
52//!     snd a
53//!     jgz f -16       }
54//!     jgz a -19       // Jump to deadlock section.
55//! ```
56use crate::util::parse::*;
57
58/// Generate a pseudorandom sequence of 127 numbers, based on a
59/// starting seed different for each input.
60pub fn parse(input: &str) -> Vec<u64> {
61    // Read the starting seed from the input.
62    let mut p: u64 = input.lines().nth(9).unwrap().unsigned();
63    let mut numbers = Vec::with_capacity(127);
64
65    // Generate pseudorandom sequence.
66    for _ in 0..127 {
67        p = (p * 8505) % 0x7fffffff;
68        p = (p * 129749 + 12345) % 0x7fffffff;
69        numbers.push(p % 10000);
70    }
71
72    numbers
73}
74
75/// Part one is the last number sent in the sequence.
76pub fn part1(input: &[u64]) -> u64 {
77    input[126]
78}
79
80/// Bubble sort the sequence into descending order, counting the number of passes.
81/// Starting with program 1 each program alternates sorting the input by sending it to the other
82/// program, so the number of passes that program 1 takes is the total divided by two rounding up.
83pub fn part2(input: &[u64]) -> usize {
84    let mut numbers = input.to_vec();
85    let mut swapped = true;
86    let mut count = 0;
87
88    // Bubble sort in descending order.
89    while swapped {
90        swapped = false;
91
92        // "Optimized" version skipping the last count elements as these are already sorted.
93        for i in 1..127 - count {
94            if numbers[i - 1] < numbers[i] {
95                numbers.swap(i - 1, i);
96                swapped = true;
97            }
98        }
99
100        count += 1;
101    }
102
103    // The sequence countains 127 numbers so the final result is multiplied by that factor.
104    127 * count.div_ceil(2)
105}