2018 day 19 - brutal intcode problem...

This commit is contained in:
alexchao26
2020-12-05 00:45:13 -05:00
parent 88b0ce7593
commit c8ee6a442a
4 changed files with 433 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
#ip 4
addi 4 16 4
seti 1 5 1
seti 1 7 3
mulr 1 3 5
eqrr 5 2 5
addr 5 4 4
addi 4 1 4
addr 1 0 0
addi 3 1 3
gtrr 3 2 5
addr 4 5 4
seti 2 4 4
addi 1 1 1
gtrr 1 2 5
addr 5 4 4
seti 1 5 4
mulr 4 4 4
addi 2 2 2
mulr 2 2 2
mulr 4 2 2
muli 2 11 2
addi 5 2 5
mulr 5 4 5
addi 5 18 5
addr 2 5 2
addr 4 0 4
seti 0 6 4
setr 4 3 5
mulr 5 4 5
addr 4 5 5
mulr 4 5 5
muli 5 14 5
mulr 5 4 5
addr 2 5 2
seti 0 2 0
seti 0 6 4
+256
View File
@@ -0,0 +1,256 @@
package main
import (
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/util"
)
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(util.ReadFile("./input.txt"))
fmt.Println("Output:", ans)
} else {
ans := part2(util.ReadFile("./input.txt"))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
opcodeComputer := parseInput(input)
for !opcodeComputer.tick() {
}
return opcodeComputer.registers[0]
}
func part2(input string) int {
opcodeComputer := parseInput(input)
opcodeComputer.registers[0] = 1
for !opcodeComputer.tick() {
}
return opcodeComputer.registers[0]
}
// after deeply studying how the intcode cycles, in order to optimize it and
// run in any reasonable amount of time. It's become clear that the answer is
// just the sum of all the factors of the number that is generated after a few
// steps of the opcode computer running
func part2Cheeky(input string) int {
computer := parseInput(input)
computer.registers[0] = 1
for i := 0; i < 20; i++ {
computer.tick()
}
numberToFactorize := computer.registers[2] // this index varies based on inputs
var ans int
for i := 1; i <= numberToFactorize; i++ {
if numberToFactorize%i == 0 {
ans += i
}
}
return ans
}
type opcodeComputer struct {
instructions []instruction
registers [6]int
instructionPointer int // an index the stores the index for which instruction to run
}
type instruction struct {
name string
abcValues [3]int
}
func (o *opcodeComputer) tick() (done bool) {
// custom logic for the repetitive behavior
ipValue := o.registers[o.instructionPointer]
if ipValue == 4 {
for o.registers[4] == 4 {
if o.registers[1] >= o.registers[2] {
break
}
if o.registers[5] == o.registers[2] {
o.registers[0] += o.registers[1]
}
o.registers[3]++
reg3 := o.registers[3]
if reg3 > o.registers[2] {
o.registers[1]++
// escape hatch
if o.registers[1] > o.registers[2] {
o.registers[4] *= o.registers[4]
o.registers[4]++
break
}
o.registers[3] = 1
o.registers[5] = o.registers[1]
} else if o.registers[2]%o.registers[1] == 0 {
// increase registers 5 to hit o.reg5 == o.reg2
// side of 5 = 1 x 3
o.registers[5] = o.registers[2]
o.registers[3] = o.registers[2]
} else {
o.registers[3] = o.registers[2]
}
}
}
if o.registers[o.instructionPointer] >= len(o.instructions) {
return true
}
inst := o.instructions[o.registers[o.instructionPointer]]
opcodeFunc := opcodeNamesToFuncs[inst.name]
o.registers = opcodeFunc(o.registers, inst.abcValues)
// increment value @ instructionPointer, validate that it's still in range
o.registers[o.instructionPointer]++
if o.registers[o.instructionPointer] >= len(o.instructions) {
return true
}
return false
}
func parseInput(input string) opcodeComputer {
lines := strings.Split(input, "\n")
var instructionPointer int
fmt.Sscanf(lines[0], "#ip %d", &instructionPointer)
var instructions []instruction
for _, l := range lines[1:] {
var inst instruction
fmt.Sscanf(l, "%4s %d %d %d", &inst.name, &inst.abcValues[0], &inst.abcValues[1], &inst.abcValues[2])
instructions = append(instructions, inst)
}
// for i, v := range instructions {
// fmt.Println(i, v)
// }
return opcodeComputer{
instructions: instructions,
instructionPointer: instructionPointer,
}
}
var opcodeNamesToFuncs = map[string]opcodeFunc{
"addr": addr, "addi": addi,
"mulr": mulr, "muli": muli,
"banr": banr, "bani": bani,
"borr": borr, "bori": bori,
"setr": setr, "seti": seti,
"gtir": gtir, "gtri": gtri, "gtrr": gtrr,
"eqir": eqir, "eqri": eqri, "eqrr": eqrr,
}
type opcodeFunc func([6]int, [3]int) [6]int
func addr(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] + registers[abcValues[1]]
return registers
}
func addi(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] + abcValues[1]
return registers
}
func mulr(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] * registers[abcValues[1]]
return registers
}
func muli(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] * abcValues[1]
return registers
}
func banr(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] & registers[abcValues[1]]
return registers
}
func bani(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] & abcValues[1]
return registers
}
func borr(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] | registers[abcValues[1]]
return registers
}
func bori(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]] | abcValues[1]
return registers
}
func setr(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = registers[abcValues[0]]
return registers
}
func seti(registers [6]int, abcValues [3]int) [6]int {
registers[abcValues[2]] = abcValues[0]
return registers
}
func gtir(registers [6]int, abcValues [3]int) [6]int {
if abcValues[0] > registers[abcValues[1]] {
registers[abcValues[2]] = 1
} else {
registers[abcValues[2]] = 0
}
return registers
}
func gtri(registers [6]int, abcValues [3]int) [6]int {
if registers[abcValues[0]] > abcValues[1] {
registers[abcValues[2]] = 1
} else {
registers[abcValues[2]] = 0
}
return registers
}
func gtrr(registers [6]int, abcValues [3]int) [6]int {
if registers[abcValues[0]] > registers[abcValues[1]] {
registers[abcValues[2]] = 1
} else {
registers[abcValues[2]] = 0
}
return registers
}
func eqir(registers [6]int, abcValues [3]int) [6]int {
if abcValues[0] == registers[abcValues[1]] {
registers[abcValues[2]] = 1
} else {
registers[abcValues[2]] = 0
}
return registers
}
func eqri(registers [6]int, abcValues [3]int) [6]int {
if registers[abcValues[0]] == abcValues[1] {
registers[abcValues[2]] = 1
} else {
registers[abcValues[2]] = 0
}
return registers
}
func eqrr(registers [6]int, abcValues [3]int) [6]int {
if registers[abcValues[0]] == registers[abcValues[1]] {
registers[abcValues[2]] = 1
} else {
registers[abcValues[2]] = 0
}
return registers
}
+70
View File
@@ -0,0 +1,70 @@
package main
import (
"testing"
"github.com/alexchao26/advent-of-code-go/util"
)
var tests1 = []struct {
name string
want int
input string
// add extra args if needed
}{
{"example", 7, `#ip 0
seti 5 0 1
seti 6 0 2
addi 0 1 0
addr 1 2 3
setr 1 0 0
seti 8 0 4
seti 9 0 5`},
{"actual", 1350, util.ReadFile("input.txt")},
}
func TestPart1(t *testing.T) {
for _, tt := range tests1 {
t.Run(tt.name, func(t *testing.T) {
if got := part1(tt.input); got != tt.want {
t.Errorf("part1() = %v, want %v", got, tt.want)
}
})
}
}
var tests2 = []struct {
name string
want int
input string
// add extra args if needed
}{
{"actual", 15844608, util.ReadFile("input.txt")},
}
func TestPart2(t *testing.T) {
for _, tt := range tests2 {
t.Run(tt.name, func(t *testing.T) {
if got := part2(tt.input); got != tt.want {
t.Errorf("part2() = %v, want %v", got, tt.want)
}
})
}
}
func TestPart2Cheeky(t *testing.T) {
tests := []struct {
name string
args string
want int
}{
{"actual", util.ReadFile("input.txt"), 15844608},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := part2Cheeky(tt.args); got != tt.want {
t.Errorf("part2Cheeky() = %v, want %v", got, tt.want)
}
})
}
}
+70
View File
@@ -0,0 +1,70 @@
--- Day 19: Go With The Flow ---
With the Elves well on their way constructing the North Pole base, you turn your attention back to understanding the inner workings of programming the device.
You can't help but notice that the device's opcodes don't contain any flow control like jump instructions. The device's manual goes on to explain:
"In programs where flow control is required, the instruction pointer can be bound to a register so that it can be manipulated directly. This way, setr/seti can function as absolute jumps, addr/addi can function as relative jumps, and other opcodes can cause truly fascinating effects."
This mechanism is achieved through a declaration like #ip 1, which would modify register 1 so that accesses to it let the program indirectly access the instruction pointer itself. To compensate for this kind of binding, there are now six registers (numbered 0 through 5); the five not bound to the instruction pointer behave as normal. Otherwise, the same rules apply as the last time you worked with this device.
When the instruction pointer is bound to a register, its value is written to that register just before each instruction is executed, and the value of that register is written back to the instruction pointer immediately after each instruction finishes execution. Afterward, move to the next instruction by adding one to the instruction pointer, even if the value in the instruction pointer was just updated by an instruction. (Because of this, instructions must effectively set the instruction pointer to the instruction before the one they want executed next.)
The instruction pointer is 0 during the first instruction, 1 during the second, and so on. If the instruction pointer ever causes the device to attempt to load an instruction outside the instructions defined in the program, the program instead immediately halts. The instruction pointer starts at 0.
It turns out that this new information is already proving useful: the CPU in the device is not very powerful, and a background process is occupying most of its time. You dump the background process' declarations and instructions to a file (your puzzle input), making sure to use the names of the opcodes rather than the numbers.
For example, suppose you have the following program:
#ip 0
seti 5 0 1
seti 6 0 2
addi 0 1 0
addr 1 2 3
setr 1 0 0
seti 8 0 4
seti 9 0 5
When executed, the following instructions are executed. Each line contains the value of the instruction pointer at the time the instruction started, the values of the six registers before executing the instructions (in square brackets), the instruction itself, and the values of the six registers after executing the instruction (also in square brackets).
ip=0 [0, 0, 0, 0, 0, 0] seti 5 0 1 [0, 5, 0, 0, 0, 0]
ip=1 [1, 5, 0, 0, 0, 0] seti 6 0 2 [1, 5, 6, 0, 0, 0]
ip=2 [2, 5, 6, 0, 0, 0] addi 0 1 0 [3, 5, 6, 0, 0, 0]
ip=4 [4, 5, 6, 0, 0, 0] setr 1 0 0 [5, 5, 6, 0, 0, 0]
ip=6 [6, 5, 6, 0, 0, 0] seti 9 0 5 [6, 5, 6, 0, 0, 9]
In detail, when running this program, the following events occur:
The first line (#ip 0) indicates that the instruction pointer should be bound to register 0 in this program. This is not an instruction, and so the value of the instruction pointer does not change during the processing of this line.
The instruction pointer contains 0, and so the first instruction is executed (seti 5 0 1). It updates register 0 to the current instruction pointer value (0), sets register 1 to 5, sets the instruction pointer to the value of register 0 (which has no effect, as the instruction did not modify register 0), and then adds one to the instruction pointer.
The instruction pointer contains 1, and so the second instruction, seti 6 0 2, is executed. This is very similar to the instruction before it: 6 is stored in register 2, and the instruction pointer is left with the value 2.
The instruction pointer is 2, which points at the instruction addi 0 1 0. This is like a relative jump: the value of the instruction pointer, 2, is loaded into register 0. Then, addi finds the result of adding the value in register 0 and the value 1, storing the result, 3, back in register 0. Register 0 is then copied back to the instruction pointer, which will cause it to end up 1 larger than it would have otherwise and skip the next instruction (addr 1 2 3) entirely. Finally, 1 is added to the instruction pointer.
The instruction pointer is 4, so the instruction setr 1 0 0 is run. This is like an absolute jump: it copies the value contained in register 1, 5, into register 0, which causes it to end up in the instruction pointer. The instruction pointer is then incremented, leaving it at 6.
The instruction pointer is 6, so the instruction seti 9 0 5 stores 9 into register 5. The instruction pointer is incremented, causing it to point outside the program, and so the program ends.
What value is left in register 0 when the background process halts?
--- Part Two ---
A new background process immediately spins up in its place. It appears identical, but on closer inspection, you notice that this time, register 0 started with the value 1.
What value is left in register 0 when this new background process halts?