I’m getting caught back up, slowly!
Day 10
Day 10 was pretty quick. Both parts, but especially part 2, were sort of hacky,
but that’s okay; this script only has to run once! In an ideal world, I might
have made an iterator that generates per-cycle “bytecode” (this could be as
simple as inserting an extra noop before each addx) so that each instruction
takes exactly one cycle. But day 11 awaits, so move on we must.
Part 1 source code
use std::collections::HashMap;
use std::fs;
fn process(data: &str) -> i32 {
let mut current_cycle = 1;
let mut accumulator = 1;
let mut value_at_cycle = HashMap::new();
for line in data.split("\n") {
let instruction: Vec<&str> = line.split(" ").collect();
match instruction[0] {
"noop" => {
value_at_cycle.insert(current_cycle, accumulator);
current_cycle += 1;
}
"addx" => {
value_at_cycle.insert(current_cycle, accumulator);
current_cycle += 1;
value_at_cycle.insert(current_cycle, accumulator);
current_cycle += 1;
accumulator += instruction[1].parse::<i32>().unwrap();
}
_ => panic!("Unknown instruction {}", instruction[0]),
}
}
let mut total_signal_strength = 0;
for i in vec![20, 60, 100, 140, 180, 220] {
total_signal_strength += i * value_at_cycle[&i];
}
total_signal_strength
}
fn main() {
let data = fs::read_to_string("input.txt").unwrap();
let data = data.trim();
println!("{}", process(data));
}
#[cfg(test)]
mod test {
use super::*;
static DATA: &str = "addx 15
addx -11
addx 6
addx -3
addx 5
addx -1
addx -8
addx 13
addx 4
noop
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx -35
addx 1
addx 24
addx -19
addx 1
addx 16
addx -11
noop
noop
addx 21
addx -15
noop
noop
addx -3
addx 9
addx 1
addx -3
addx 8
addx 1
addx 5
noop
noop
noop
noop
noop
addx -36
noop
addx 1
addx 7
noop
noop
noop
addx 2
addx 6
noop
noop
noop
noop
noop
addx 1
noop
noop
addx 7
addx 1
noop
addx -13
addx 13
addx 7
noop
addx 1
addx -33
noop
noop
noop
addx 2
noop
noop
noop
addx 8
noop
addx -1
addx 2
addx 1
noop
addx 17
addx -9
addx 1
addx 1
addx -3
addx 11
noop
noop
addx 1
noop
addx 1
noop
noop
addx -13
addx -19
addx 1
addx 3
addx 26
addx -30
addx 12
addx -1
addx 3
addx 1
noop
noop
noop
addx -9
addx 18
addx 1
addx 2
noop
noop
addx 9
noop
noop
noop
addx -1
addx 2
addx -37
addx 1
addx 3
noop
addx 15
addx -21
addx 22
addx -6
addx 1
noop
addx 2
addx 1
noop
addx -10
noop
noop
addx 20
addx 1
addx 2
addx 2
addx -6
addx -11
noop
noop
noop";
#[test]
fn test() {
assert!(process(DATA) == 13140);
}
}
Part 2 source code
use std::collections::HashMap;
use std::fs;
fn process(data: &str) -> i32 {
let mut current_cycle = 1;
let mut accumulator = 1;
let mut value_at_cycle = HashMap::new();
for line in data.split("\n") {
let instruction: Vec<&str> = line.split(" ").collect();
match instruction[0] {
"noop" => {
value_at_cycle.insert(current_cycle, accumulator);
current_cycle += 1;
}
"addx" => {
value_at_cycle.insert(current_cycle, accumulator);
current_cycle += 1;
value_at_cycle.insert(current_cycle, accumulator);
current_cycle += 1;
accumulator += instruction[1].parse::<i32>().unwrap();
}
_ => panic!("Unknown instruction {}", instruction[0]),
}
}
let mut total_signal_strength = 0;
for i in vec![20, 60, 100, 140, 180, 220] {
total_signal_strength += i * value_at_cycle[&i];
}
total_signal_strength
}
fn main() {
let data = fs::read_to_string("input.txt").unwrap();
let data = data.trim();
println!("{}", process(data));
}
#[cfg(test)]
mod test {
use super::*;
static DATA: &str = "addx 15
addx -11
addx 6
addx -3
addx 5
addx -1
addx -8
addx 13
addx 4
noop
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx -35
addx 1
addx 24
addx -19
addx 1
addx 16
addx -11
noop
noop
addx 21
addx -15
noop
noop
addx -3
addx 9
addx 1
addx -3
addx 8
addx 1
addx 5
noop
noop
noop
noop
noop
addx -36
noop
addx 1
addx 7
noop
noop
noop
addx 2
addx 6
noop
noop
noop
noop
noop
addx 1
noop
noop
addx 7
addx 1
noop
addx -13
addx 13
addx 7
noop
addx 1
addx -33
noop
noop
noop
addx 2
noop
noop
noop
addx 8
noop
addx -1
addx 2
addx 1
noop
addx 17
addx -9
addx 1
addx 1
addx -3
addx 11
noop
noop
addx 1
noop
addx 1
noop
noop
addx -13
addx -19
addx 1
addx 3
addx 26
addx -30
addx 12
addx -1
addx 3
addx 1
noop
noop
noop
addx -9
addx 18
addx 1
addx 2
noop
noop
addx 9
noop
noop
noop
addx -1
addx 2
addx -37
addx 1
addx 3
noop
addx 15
addx -21
addx 22
addx -6
addx 1
noop
addx 2
addx 1
noop
addx -10
noop
noop
addx 20
addx 1
addx 2
addx 2
addx -6
addx -11
noop
noop
noop";
#[test]
fn test() {
assert!(process(DATA) == 13140);
}
}
Day 11
Today’s round 2 was a bit of a brainteaser. For round 1, I’d been doing what I
imagine was intended: Make the number a sufficiently large type (I’m using
usize, the pointer-sized uint, which is 64-bit on my platform), and rely on
the division by three to keep things manageable. For a limited number of rounds
and with repeated division by three, these numbers stay manageable; however, for
this part of the puzzle, that’s not quite the case any more!
Since all of our tests are testing for divisibility, it’s modular arithmetic to the rescue! But suppose I have three divisibility conditions: divisible by 2, by 3, and by 5. If I use 2, or 3, or 5 as the modulus, that successfully shrinks my total, but at the expense of being completely wrong! So we can instead take the least common multiple of these; or, more simply, just multiply them all together! For my input, this results in a number less than a hundred thousand; very reasonable. This would fit fine in an u32, and certainly in a u64 with no trouble. (If this weren’t the case, repeated passes through the hands of the monkey that multiples the worry by itself would rapidly run me out of space; each squaring doubles the number of bits that my answer needs to use, so I have a maximum of 64 passes through that monkey’s hands before I’m out of space!)
I’ve been busy enough with the puzzles that I haven’t been taking much time to
engross myself in new emacs features; instead, I’m focusing on refining my
knowledge of the features I know, and retraining my muscle memory from Vim. That
said, next up on the list is
Version Control and/or
Magit (or another of Emacs’ many
git-related tools). I may also have to
learn a bit about installing and managing packages, editing my emacs config
file, and writing elisp. On to tomorrow!
Source code
use std::collections::VecDeque;
use std::fs;
#[derive(Eq, PartialEq, Debug)]
struct Monkey {
starting_items: VecDeque<usize>,
operation: Vec<String>,
test_divisible_by_cond: usize,
target_if_test: usize,
target_if_not_test: usize,
inspected_item_count: usize,
}
fn update_worry_level(operation: &Vec<String>, level: usize) -> usize {
if operation[4] == "old" {
// new = old * old
level * level
} else {
match operation[3].as_str() {
"*" => level * operation[4].parse::<usize>().unwrap(),
"+" => level + operation[4].parse::<usize>().unwrap(),
_ => panic!("Unknown operator {}", operation[3]),
}
}
}
fn parse(data: &str) -> Vec<Monkey> {
let mut monkeys = Vec::new();
for monkey_data in data.split("\n\n") {
let lines: Vec<&str> = monkey_data.split("\n").collect();
monkeys.push(Monkey {
starting_items: lines[1][18..]
.split(", ")
.map(|i| i.parse().unwrap())
.collect(),
operation: lines[2][13..].split(" ").map(|s| s.to_string()).collect(),
test_divisible_by_cond: lines[3][21..].parse().unwrap(),
target_if_test: lines[4][29..].parse().unwrap(),
target_if_not_test: lines[5][30..].parse().unwrap(),
inspected_item_count: 0,
});
}
monkeys
}
fn process(monkeys: &mut Vec<Monkey>, rounds: usize) -> usize {
let test_product: usize = monkeys.iter().map(|m| m.test_divisible_by_cond).product();
for _round in 0..rounds {
for i in 0..monkeys.len() {
while monkeys[i].starting_items.len() > 0 {
monkeys[i].inspected_item_count += 1;
let mut item = monkeys[i].starting_items.pop_front().unwrap();
item = update_worry_level(&monkeys[i].operation, item);
// item /= 3; // part 1 only
item %= test_product; // part 2 only
let target_monkey = if item % monkeys[i].test_divisible_by_cond == 0 {
monkeys[i].target_if_test
} else {
monkeys[i].target_if_not_test
};
monkeys[target_monkey].starting_items.push_back(item);
}
}
}
let mut access_counts: Vec<usize> = monkeys.iter().map(|m| m.inspected_item_count).collect();
access_counts.sort_unstable_by(|a, b| b.cmp(a));
access_counts[0] * access_counts[1]
}
fn main() {
let data = fs::read_to_string("input.txt").unwrap();
let data = data.trim();
let mut monkeys = parse(data);
println!("{}", process(&mut monkeys, 10000)); // 20 for part 1
}
#[cfg(test)]
mod test {
use super::*;
static DATA: &str = "Monkey 0:
Starting items: 79, 98
Operation: new = old * 19
Test: divisible by 23
If true: throw to monkey 2
If false: throw to monkey 3
Monkey 1:
Starting items: 54, 65, 75, 74
Operation: new = old + 6
Test: divisible by 19
If true: throw to monkey 2
If false: throw to monkey 0
Monkey 2:
Starting items: 79, 60, 97
Operation: new = old * old
Test: divisible by 13
If true: throw to monkey 1
If false: throw to monkey 3
Monkey 3:
Starting items: 74
Operation: new = old + 3
Test: divisible by 17
If true: throw to monkey 0
If false: throw to monkey 1";
#[test]
fn test_parse_one_monkey() {
let data = "Monkey 0:
Starting items: 75, 63
Operation: new = old * 3
Test: divisible by 11
If true: throw to monkey 7
If false: throw to monkey 2";
let monkeys = parse(data);
println!("{:?}", monkeys);
let goal_monkeys = vec![Monkey {
starting_items: VecDeque::from([75, 63]),
operation: vec!["new", "=", "old", "*", "3"]
.iter()
.map(|s| s.to_string())
.collect(),
test_divisible_by_cond: 11,
target_if_test: 7,
target_if_not_test: 2,
inspected_item_count: 0,
}];
println!("Goal: {:?}\nActual: {:?}", goal_monkeys, monkeys);
assert!(goal_monkeys == monkeys);
}
#[test]
fn test() {
let desired_results = vec![
(1, 4 * 6),
(20, 99 * 103),
(1000, 5204 * 5192),
(2000, 10419 * 10391),
(3000, 15638 * 15593),
(4000, 20858 * 20797),
(5000, 26075 * 26000),
(6000, 31294 * 31204),
(7000, 36508 * 36400),
(8000, 41728 * 41606),
(9000, 46945 * 46807),
(10000, 52166 * 52013),
];
for (rounds, desired_result) in desired_results {
let mut monkeys = parse(DATA);
let result = process(&mut monkeys, rounds);
println!("{} == {}", result, desired_result);
assert!(result == desired_result);
}
}
}

