package main import ( "fmt" "slices" "strconv" ) func main() { fmt.Println(day17(1)) fmt.Println(day17(2)) } func day17(part int) string { a, b, c, program := parseRegisters() if part == 1 { // part 1: run program and return comma-separated output return fmt.Sprintln(runProgram(a, b, c, program)) } // part 2: find lowest value of register A that makes the program output itself a = 0 // the initial value of register A doesnt matter here, so we can reset it for pos := len(program) - 1; pos >= 0; pos-- { a <<= 3 // shift left by 3 bits for !slices.Equal(runProgram(a, b, c, program), program[pos:]) { a++ } } return strconv.Itoa(a) // return a string since part 1 needs a string } func parseRegisters() (int, int, int, []int) { return 33940147, 0, 0, []int{2, 4, 1, 5, 7, 5, 1, 6, 4, 2, 5, 5, 0, 3, 3, 0} } func runProgram(a, b, c int, program []int) []int { out := make([]int, 0) // for each isntruction pointer for ip := 0; ip < len(program); ip += 2 { opcode, operand := program[ip], program[ip+1] // Process combo operand value := operand switch operand { case 4: value = a case 5: value = b case 6: value = c } // Execute instruction switch opcode { case 0: // adv - divide A by 2^value a >>= value case 1: // bxl - XOR B with literal b ^= operand case 2: // bst - set B to value mod 8 b = value % 8 case 3: // jnz - jump if A is not zero if a != 0 { ip = operand - 2 } case 4: // bxc - XOR B with C b ^= c case 5: // out - output value mod 8 out = append(out, value%8) case 6: // bdv - divide A by 2^value, store in B b = a >> value case 7: // cdv - divide A by 2^value, store in C c = a >> value } } return out }