138 lines
3.2 KiB
Rust
138 lines
3.2 KiB
Rust
use std::io::BufRead;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
enum Op {
|
|
Acc,
|
|
Nop,
|
|
Jmp,
|
|
}
|
|
|
|
impl FromStr for Op {
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Ok(match s {
|
|
"acc" => Op::Acc,
|
|
"nop" => Op::Nop,
|
|
"jmp" => Op::Jmp,
|
|
_ => unreachable!()
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
struct Instruction {
|
|
op: Op,
|
|
param: isize,
|
|
executed: bool,
|
|
}
|
|
|
|
impl FromStr for Instruction {
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut it = s.split(' ');
|
|
Ok(Instruction {
|
|
op: it.next().map(|s| s.parse().ok()).flatten().unwrap(),
|
|
param: it.next().unwrap().parse().unwrap(),
|
|
executed: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn parse_boot_code<F: BufRead> (input: F) -> Vec<Instruction> {
|
|
input.lines()
|
|
.filter_map(|line| line.unwrap().parse().ok())
|
|
.collect()
|
|
}
|
|
|
|
fn run_boot_code(mut boot_code: Vec<Instruction>) -> Result<isize, isize> {
|
|
let mut intrustction_pointer: isize = 0;
|
|
let mut acc = 0;
|
|
|
|
while (intrustction_pointer as usize) < boot_code.len() {
|
|
let current_instruction = &mut boot_code[intrustction_pointer as usize];
|
|
if current_instruction.executed {
|
|
return Err(acc)
|
|
} else {
|
|
current_instruction.executed = true;
|
|
match current_instruction.op {
|
|
Op::Acc => {
|
|
acc += current_instruction.param;
|
|
intrustction_pointer += 1;
|
|
},
|
|
Op::Nop => intrustction_pointer += 1,
|
|
Op::Jmp => intrustction_pointer += current_instruction.param,
|
|
}
|
|
}
|
|
}
|
|
Ok(acc)
|
|
}
|
|
|
|
pub fn part1<F: BufRead> (input: F) {
|
|
println!("{}", run_boot_code(parse_boot_code(input)).unwrap_err());
|
|
}
|
|
|
|
pub fn part2<F: BufRead> (input: F) {
|
|
let boot_code = parse_boot_code(input);
|
|
let output = boot_code.iter().enumerate().filter_map(|(idx, instruction)| {
|
|
let mut test_code = boot_code.clone();
|
|
test_code[idx].op = match instruction.op {
|
|
Op::Nop => Op::Jmp,
|
|
Op::Jmp => Op::Nop,
|
|
_ => return None,
|
|
};
|
|
run_boot_code(test_code).ok()
|
|
}).next().unwrap();
|
|
println!("{}", output);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
#[test]
|
|
pub fn test_parse() {
|
|
let inputs = [
|
|
"nop +0",
|
|
"acc +1",
|
|
"jmp -4"
|
|
];
|
|
let expected = [
|
|
Instruction {
|
|
op: Op::Nop,
|
|
param: 0,
|
|
executed: false
|
|
},
|
|
Instruction {
|
|
op: Op::Acc,
|
|
param: 1,
|
|
executed: false
|
|
},
|
|
Instruction {
|
|
op: Op::Jmp,
|
|
param: -4,
|
|
executed: false
|
|
},
|
|
];
|
|
for (input, item) in inputs.iter().zip(expected.iter()) {
|
|
assert_eq!(input.parse::<Instruction>().unwrap(), *item);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_boot_code_loop() {
|
|
let input = r#"nop +0
|
|
acc +1
|
|
jmp +4
|
|
acc +3
|
|
jmp -3
|
|
acc -99
|
|
acc +1
|
|
jmp -4
|
|
acc +6"#.as_bytes();
|
|
|
|
assert_eq!(Err(5), run_boot_code(parse_boot_code(input)));
|
|
}
|
|
}
|