Add 2024 Day 17
This commit is contained in:
parent
a4460964fb
commit
bb8fc8c5f0
5 changed files with 295 additions and 0 deletions
13
2024/day17_chronospatial_computer/Cargo.toml
Normal file
13
2024/day17_chronospatial_computer/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "day17_chronospatial_computer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
name = "test_benchmark"
|
||||
harness = false
|
92
2024/day17_chronospatial_computer/challenge.md
Normal file
92
2024/day17_chronospatial_computer/challenge.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
The Historians push the button on their strange device, but this time, you all just feel like you're [falling](/2018/day/6).
|
||||
|
||||
"Situation critical", the device announces in a familiar voice. "Bootstrapping process failed. Initializing debugger...."
|
||||
|
||||
The small handheld device suddenly unfolds into an entire computer! The Historians look around nervously before one of them tosses it to you.
|
||||
|
||||
This seems to be a 3-bit computer: its program is a list of 3-bit numbers (0 through 7), like `0,1,2,3`. The computer also has three *registers* named `A`, `B`, and `C`, but these registers aren't limited to 3 bits and can instead hold any integer.
|
||||
|
||||
The computer knows *eight instructions*, each identified by a 3-bit number (called the instruction's *opcode*). Each instruction also reads the 3-bit number after it as an input; this is called its *operand*.
|
||||
|
||||
A number called the *instruction pointer* identifies the position in the program from which the next opcode will be read; it starts at `0`, pointing at the first 3-bit number in the program. Except for jump instructions, the instruction pointer increases by `2` after each instruction is processed (to move past the instruction's opcode and its operand). If the computer tries to read an opcode past the end of the program, it instead *halts*.
|
||||
|
||||
So, the program `0,1,2,3` would run the instruction whose opcode is `0` and pass it the operand `1`, then run the instruction having opcode `2` and pass it the operand `3`, then halt.
|
||||
|
||||
There are two types of operands; each instruction specifies the type of its operand. The value of a *literal operand* is the operand itself. For example, the value of the literal operand `7` is the number `7`. The value of a *combo operand* can be found as follows:
|
||||
|
||||
* Combo operands `0` through `3` represent literal values `0` through `3`.
|
||||
* Combo operand `4` represents the value of register `A`.
|
||||
* Combo operand `5` represents the value of register `B`.
|
||||
* Combo operand `6` represents the value of register `C`.
|
||||
* Combo operand `7` is reserved and will not appear in valid programs.
|
||||
|
||||
The eight instructions are as follows:
|
||||
|
||||
The `*adv*` instruction (opcode `*0*`) performs *division*. The numerator is the value in the `A` register. The denominator is found by raising 2 to the power of the instruction's *combo* operand. (So, an operand of `2` would divide `A` by `4` (`2^2`); an operand of `5` would divide `A` by `2^B`.) The result of the division operation is *truncated* to an integer and then written to the `A` register.
|
||||
|
||||
The `*bxl*` instruction (opcode `*1*`) calculates the [bitwise XOR](https://en.wikipedia.org/wiki/Bitwise_operation#XOR) of register `B` and the instruction's *literal* operand, then stores the result in register `B`.
|
||||
|
||||
The `*bst*` instruction (opcode `*2*`) calculates the value of its *combo* operand [modulo](https://en.wikipedia.org/wiki/Modulo) 8 (thereby keeping only its lowest 3 bits), then writes that value to the `B` register.
|
||||
|
||||
The `*jnz*` instruction (opcode `*3*`) does *nothing* if the `A` register is `0`. However, if the `A` register is *not zero*, it *jumps* by setting the instruction pointer to the value of its *literal* operand; if this instruction jumps, the instruction pointer is *not* increased by `2` after this instruction.
|
||||
|
||||
The `*bxc*` instruction (opcode `*4*`) calculates the *bitwise XOR* of register `B` and register `C`, then stores the result in register `B`. (For legacy reasons, this instruction reads an operand but *ignores* it.)
|
||||
|
||||
The `*out*` instruction (opcode `*5*`) calculates the value of its *combo* operand modulo 8, then *outputs* that value. (If a program outputs multiple values, they are separated by commas.)
|
||||
|
||||
The `*bdv*` instruction (opcode `*6*`) works exactly like the `adv` instruction except that the result is stored in the *`B` register*. (The numerator is still read from the `A` register.)
|
||||
|
||||
The `*cdv*` instruction (opcode `*7*`) works exactly like the `adv` instruction except that the result is stored in the *`C` register*. (The numerator is still read from the `A` register.)
|
||||
|
||||
Here are some examples of instruction operation:
|
||||
|
||||
* If register `C` contains `9`, the program `2,6` would set register `B` to `1`.
|
||||
* If register `A` contains `10`, the program `5,0,5,1,5,4` would output `0,1,2`.
|
||||
* If register `A` contains `2024`, the program `0,1,5,4,3,0` would output `4,2,5,6,7,7,7,7,3,1,0` and leave `0` in register `A`.
|
||||
* If register `B` contains `29`, the program `1,7` would set register `B` to `26`.
|
||||
* If register `B` contains `2024` and register `C` contains `43690`, the program `4,0` would set register `B` to `44354`.
|
||||
|
||||
The Historians' strange device has finished initializing its debugger and is displaying some *information about the program it is trying to run* (your puzzle input). For example:
|
||||
|
||||
```
|
||||
Register A: 729
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 0,1,5,4,3,0
|
||||
|
||||
```
|
||||
|
||||
Your first task is to *determine what the program is trying to output*. To do this, initialize the registers to the given values, then run the given program, collecting any output produced by `out` instructions. (Always join the values produced by `out` instructions with commas.) After the above program halts, its final output will be `*4,6,3,5,6,3,5,2,1,0*`.
|
||||
|
||||
Using the information provided by the debugger, initialize the registers to the given values, then run the program. Once it halts, *what do you get if you use commas to join the values it output into a single string?*
|
||||
|
||||
Your puzzle answer was `7,4,2,5,1,4,6,0,4`.
|
||||
|
||||
\--- Part Two ---
|
||||
----------
|
||||
|
||||
Digging deeper in the device's manual, you discover the problem: this program is supposed to *output another copy of the program*! Unfortunately, the value in register `A` seems to have been corrupted. You'll need to find a new value to which you can initialize register `A` so that the program's output instructions produce an exact copy of the program itself.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
Register A: 2024
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 0,3,5,4,3,0
|
||||
|
||||
```
|
||||
|
||||
This program outputs a copy of itself if register `A` is instead initialized to `*117440*`. (The original initial value of register `A`, `2024`, is ignored.)
|
||||
|
||||
*What is the lowest positive initial value for register `A` that causes the program to output a copy of itself?*
|
||||
|
||||
Your puzzle answer was `164278764924605`.
|
||||
|
||||
Both parts of this puzzle are complete! They provide two gold stars: \*\*
|
||||
|
||||
At this point, you should [return to your Advent calendar](/2024) and try another puzzle.
|
||||
|
||||
If you still want to see it, you can [get your puzzle input](17/input).
|
180
2024/day17_chronospatial_computer/src/lib.rs
Normal file
180
2024/day17_chronospatial_computer/src/lib.rs
Normal file
|
@ -0,0 +1,180 @@
|
|||
use core::fmt::Display;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
LineCount,
|
||||
ProgramNotSet,
|
||||
RegisterNotSet(u8),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::LineCount => write!(f, "Input must consist of 5 lines: One setting each register, an empty line, and the program"),
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
Self::ProgramNotSet => write!(f, "Line 5 must contain the program at its last position (separated by whitespace)"),
|
||||
Self::RegisterNotSet(n) => write!(f, "Line {n} must contain the value for register {} at its last position (separated by whitespace)", b'@' + n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct Computer {
|
||||
opcode: Vec<u8>,
|
||||
instruction_ptr: usize,
|
||||
registers: [usize; 3],
|
||||
output: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Computer {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
let lines: Vec<_> = value.lines().collect();
|
||||
if lines.len() != 5 {
|
||||
return Err(Self::Error::LineCount);
|
||||
}
|
||||
let a = lines[0].split_whitespace().last().ok_or(Self::Error::RegisterNotSet(1))?.parse::<usize>()?;
|
||||
let b = lines[1].split_whitespace().last().ok_or(Self::Error::RegisterNotSet(2))?.parse::<usize>()?;
|
||||
let c = lines[2].split_whitespace().last().ok_or(Self::Error::RegisterNotSet(3))?.parse::<usize>()?;
|
||||
|
||||
let registers = [a, b, c];
|
||||
|
||||
let opcode = lines[4]
|
||||
.split_whitespace()
|
||||
.last()
|
||||
.ok_or(Self::Error::ProgramNotSet)?
|
||||
.split(',')
|
||||
.map(|n| n.parse::<u8>())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
opcode,
|
||||
registers,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Computer {
|
||||
fn combo(&self, operand: usize) -> usize {
|
||||
if operand < 4 {
|
||||
operand
|
||||
} else {
|
||||
self.registers[operand - 4]
|
||||
}
|
||||
}
|
||||
|
||||
fn adv(&mut self, operand: usize) {
|
||||
self.registers[0] >>= self.combo(operand);
|
||||
}
|
||||
|
||||
fn bxl(&mut self, operand: usize) {
|
||||
self.registers[1] ^= operand
|
||||
}
|
||||
|
||||
fn bst(&mut self, operand: usize) {
|
||||
self.registers[1] = self.combo(operand) & 7;
|
||||
}
|
||||
|
||||
fn jnz(&mut self, operand: usize) {
|
||||
self.instruction_ptr = if self.registers[0] == 0 {
|
||||
self.instruction_ptr + 2
|
||||
} else {
|
||||
operand
|
||||
};
|
||||
}
|
||||
|
||||
fn bxc(&mut self) {
|
||||
self.registers[1] ^= self.registers[2];
|
||||
}
|
||||
|
||||
fn out(&mut self, operand: usize) {
|
||||
self.output.push((self.combo(operand) & 7) as u8);
|
||||
}
|
||||
|
||||
fn bdv(&mut self, operand: usize) {
|
||||
self.registers[1] = self.registers[0] >> self.combo(operand);
|
||||
}
|
||||
|
||||
fn cdv(&mut self, operand: usize) {
|
||||
self.registers[2] = self.registers[0] >> self.combo(operand);
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
while self.instruction_ptr < self.opcode.len()-1 {
|
||||
let instruction = self.opcode[self.instruction_ptr];
|
||||
let operand = self.opcode[self.instruction_ptr+1] as usize;
|
||||
match instruction {
|
||||
0 => self.adv(operand),
|
||||
1 => self.bxl(operand),
|
||||
2 => self.bst(operand),
|
||||
3 => self.jnz(operand),
|
||||
4 => self.bxc(),
|
||||
5 => self.out(operand),
|
||||
6 => self.bdv(operand),
|
||||
7 => self.cdv(operand),
|
||||
_ => unreachable!()
|
||||
}
|
||||
if instruction != 3 {
|
||||
self.instruction_ptr += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(input: &str) -> Result<(String, usize), ParseError> {
|
||||
let computer = Computer::try_from(input)?;
|
||||
let mut computer_1 = computer.clone();
|
||||
computer_1.run();
|
||||
let first = computer_1.output.iter().map(|n| n.to_string()).collect::<Vec<_>>().join(",");
|
||||
|
||||
// For part 2, we take advantage of the fact that both inputs are
|
||||
// structured in a way that makes every nth last output only dependent
|
||||
// on the first n octal digits of A, excluding leading zeros.
|
||||
let mut possible_starts = Vec::from([0]);
|
||||
let mut next_possible_starts: Vec<usize>;
|
||||
for idx in (0..computer.opcode.len()).rev() {
|
||||
next_possible_starts = possible_starts
|
||||
.iter()
|
||||
.flat_map(|&start| (start << 3 .. (start + 1) << 3).filter(|&a| {
|
||||
let mut computer = computer.clone();
|
||||
computer.registers[0] = a;
|
||||
computer.run();
|
||||
computer.output == computer.opcode[idx..]
|
||||
})).collect();
|
||||
std::mem::swap(&mut possible_starts, &mut next_possible_starts);
|
||||
}
|
||||
let second = *possible_starts.iter().min().unwrap();
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run(&sample_input), Ok(("5,7,3,0".to_string(), 117440)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), Ok(("7,4,2,5,1,4,6,0,4".to_string(), 164278764924605)));
|
||||
}
|
||||
}
|
5
2024/day17_chronospatial_computer/tests/challenge_input
Normal file
5
2024/day17_chronospatial_computer/tests/challenge_input
Normal file
|
@ -0,0 +1,5 @@
|
|||
Register A: 17323786
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 2,4,1,1,7,5,1,5,4,1,5,5,0,3,3,0
|
5
2024/day17_chronospatial_computer/tests/sample_input
Normal file
5
2024/day17_chronospatial_computer/tests/sample_input
Normal file
|
@ -0,0 +1,5 @@
|
|||
Register A: 2024
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 0,3,5,4,3,0
|
Loading…
Add table
Add a link
Reference in a new issue