From 658829657b2d26abb8d5f712c70fa70bc8bee11e Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Thu, 5 Dec 2019 16:53:40 +0100 Subject: [PATCH] Add solution for Day 5 Signed-off-by: Knut Ahlers --- .gitignore | 1 + Makefile | 6 ++ day02.go | 50 +-------------- day05.go | 72 +++++++++++++++++++++ day05_input.txt | 1 + day05_test.go | 21 ++++++ intcode.go | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ intcode_test.go | 152 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 421 insertions(+), 48 deletions(-) create mode 100644 Makefile create mode 100644 day05.go create mode 100644 day05_input.txt create mode 100644 day05_test.go create mode 100644 intcode.go create mode 100644 intcode_test.go diff --git a/.gitignore b/.gitignore index b47e8b9..7d13abc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/cover day03_debug.png diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..58baba9 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +default: fulltest + +fulltest: + mkdir -p cover + go test -cover -coverprofile ./cover/cover.profile -v *.go + go tool cover -html ./cover/cover.profile -o ./cover/index.html diff --git a/day02.go b/day02.go index 3a67245..5a249a1 100644 --- a/day02.go +++ b/day02.go @@ -2,61 +2,15 @@ package aoc2019 import ( "io/ioutil" - "strconv" "strings" "github.com/pkg/errors" ) -func parseDay02Intcode(code string) ([]int, error) { - parts := strings.Split(code, ",") - - var out []int - for _, n := range parts { - v, err := strconv.Atoi(n) - if err != nil { - return nil, err - } - out = append(out, v) - } - - return out, nil -} +func parseDay02Intcode(code string) ([]int, error) { return parseIntcode(code) } func executeDay02Intcode(code []int) ([]int, error) { - var ( - pos int - run = true - ) - - for run { - if pos >= len(code) { - return nil, errors.Errorf("Code position out of bounds: %d (len=%d)", pos, len(code)) - } - - switch code[pos] { - case 1: - // addition - p1, p2, pt := code[pos+1], code[pos+2], code[pos+3] - code[pt] = code[p1] + code[p2] - - case 2: - // multiplication - p1, p2, pt := code[pos+1], code[pos+2], code[pos+3] - code[pt] = code[p1] * code[p2] - - case 99: - // program finished - run = false - - default: - return nil, errors.Errorf("Encountered invalid operation %d", code[pos]) - } - - pos += 4 - } - - return code, nil + return executeIntcode(code, nil, nil) // Day02 intcode may not contain I/O } func solveDay2Part1(inFile string) (int, error) { diff --git a/day05.go b/day05.go new file mode 100644 index 0000000..38c23f5 --- /dev/null +++ b/day05.go @@ -0,0 +1,72 @@ +package aoc2019 + +import ( + "io/ioutil" + "strings" + + "github.com/pkg/errors" +) + +func solveDay5FromFile(inFile string, diagProgram int) (int, error) { + raw, err := ioutil.ReadFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read input") + } + + code, err := parseIntcode(strings.TrimSpace(string(raw))) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse Intcode") + } + + var ( + in = make(chan int, 1) + out = make(chan int, len(code)) // There cannot be more outputs than number of code parts + outputs []int + ) + + /* + * The TEST diagnostic program will start by requesting from the user + * the ID of the system to test by running an input instruction - provide + * it 1, the ID for the ship's air conditioner unit. + */ + in <- diagProgram + + if _, err := executeIntcode(code, in, out); err != nil { + return 0, errors.Wrap(err, "Program execution failed") + } + + for o := range out { + outputs = append(outputs, o) + } + + if len(outputs) < 1 { + return 0, errors.New("Program did not yield any output") + } + + for i, check := range outputs[:len(outputs)-1] { + if check != 0 { + return 0, errors.Errorf("Non-zero check result in position %d: %d", i, check) + } + } + + return outputs[len(outputs)-1], nil +} + +func solveDay5Part1(inFile string) (int, error) { + /* + * The TEST diagnostic program will start by requesting from the user + * the ID of the system to test by running an input instruction - provide + * it 1, the ID for the ship's air conditioner unit. + */ + return solveDay5FromFile(inFile, 1) +} + +func solveDay5Part2(inFile string) (int, error) { + /* + * This time, when the TEST diagnostic program runs its input + * instruction to get the ID of the system to test, provide it 5, + * the ID for the ship's thermal radiator controller. This + * diagnostic test suite only outputs one number, the diagnostic code. + */ + return solveDay5FromFile(inFile, 5) +} diff --git a/day05_input.txt b/day05_input.txt new file mode 100644 index 0000000..2667751 --- /dev/null +++ b/day05_input.txt @@ -0,0 +1 @@ +3,225,1,225,6,6,1100,1,238,225,104,0,1002,92,42,224,1001,224,-3444,224,4,224,102,8,223,223,101,4,224,224,1,224,223,223,1102,24,81,225,1101,89,36,224,101,-125,224,224,4,224,102,8,223,223,101,5,224,224,1,224,223,223,2,118,191,224,101,-880,224,224,4,224,1002,223,8,223,1001,224,7,224,1,224,223,223,1102,68,94,225,1101,85,91,225,1102,91,82,225,1102,85,77,224,101,-6545,224,224,4,224,1002,223,8,223,101,7,224,224,1,223,224,223,1101,84,20,225,102,41,36,224,101,-3321,224,224,4,224,1002,223,8,223,101,7,224,224,1,223,224,223,1,188,88,224,101,-183,224,224,4,224,1002,223,8,223,1001,224,7,224,1,224,223,223,1001,84,43,224,1001,224,-137,224,4,224,102,8,223,223,101,4,224,224,1,224,223,223,1102,71,92,225,1101,44,50,225,1102,29,47,225,101,7,195,224,101,-36,224,224,4,224,102,8,223,223,101,6,224,224,1,223,224,223,4,223,99,0,0,0,677,0,0,0,0,0,0,0,0,0,0,0,1105,0,99999,1105,227,247,1105,1,99999,1005,227,99999,1005,0,256,1105,1,99999,1106,227,99999,1106,0,265,1105,1,99999,1006,0,99999,1006,227,274,1105,1,99999,1105,1,280,1105,1,99999,1,225,225,225,1101,294,0,0,105,1,0,1105,1,99999,1106,0,300,1105,1,99999,1,225,225,225,1101,314,0,0,106,0,0,1105,1,99999,107,677,677,224,1002,223,2,223,1006,224,329,1001,223,1,223,1108,226,677,224,102,2,223,223,1006,224,344,101,1,223,223,1107,226,226,224,1002,223,2,223,1006,224,359,101,1,223,223,8,677,226,224,1002,223,2,223,1006,224,374,1001,223,1,223,1107,677,226,224,102,2,223,223,1005,224,389,1001,223,1,223,1008,677,677,224,1002,223,2,223,1006,224,404,1001,223,1,223,108,677,677,224,102,2,223,223,1005,224,419,1001,223,1,223,1107,226,677,224,102,2,223,223,1006,224,434,101,1,223,223,1008,226,226,224,1002,223,2,223,1006,224,449,1001,223,1,223,107,226,226,224,102,2,223,223,1006,224,464,1001,223,1,223,1007,677,226,224,1002,223,2,223,1006,224,479,1001,223,1,223,1108,226,226,224,102,2,223,223,1006,224,494,1001,223,1,223,8,226,226,224,1002,223,2,223,1005,224,509,1001,223,1,223,7,226,677,224,102,2,223,223,1005,224,524,101,1,223,223,1008,677,226,224,102,2,223,223,1005,224,539,101,1,223,223,107,226,677,224,1002,223,2,223,1006,224,554,1001,223,1,223,1108,677,226,224,102,2,223,223,1005,224,569,101,1,223,223,108,226,226,224,1002,223,2,223,1005,224,584,1001,223,1,223,7,677,226,224,1002,223,2,223,1005,224,599,1001,223,1,223,108,226,677,224,1002,223,2,223,1006,224,614,101,1,223,223,1007,677,677,224,1002,223,2,223,1006,224,629,101,1,223,223,7,677,677,224,102,2,223,223,1005,224,644,101,1,223,223,1007,226,226,224,1002,223,2,223,1006,224,659,1001,223,1,223,8,226,677,224,102,2,223,223,1005,224,674,1001,223,1,223,4,223,99,226 diff --git a/day05_test.go b/day05_test.go new file mode 100644 index 0000000..de9d639 --- /dev/null +++ b/day05_test.go @@ -0,0 +1,21 @@ +package aoc2019 + +import "testing" + +func TestCalculateDay5_Part1(t *testing.T) { + result, err := solveDay5Part1("day05_input.txt") + if err != nil { + t.Fatalf("Day 5 solver failed: %s", err) + } + + t.Logf("Solution Day 5 Part 1: %d", result) +} + +func TestCalculateDay5_Part2(t *testing.T) { + count, err := solveDay5Part2("day05_input.txt") + if err != nil { + t.Fatalf("Day 5 solver failed: %s", err) + } + + t.Logf("Solution Day 5 Part 2: %d", count) +} diff --git a/intcode.go b/intcode.go new file mode 100644 index 0000000..5082e49 --- /dev/null +++ b/intcode.go @@ -0,0 +1,166 @@ +package aoc2019 + +import ( + "reflect" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +type opCodeFlag int + +const ( + opCodeFlagPosition opCodeFlag = iota + opCodeFlagImmediate +) + +type opCodeType int + +const ( + opCodeTypeAddition opCodeType = 1 // Day 02 + opCodeTypeMultiplication opCodeType = 2 // Day 02 + opCodeTypeInput opCodeType = 3 // Day 05 P1 + opCodeTypeOutput opCodeType = 4 // Day 05 P1 + opCodeTypeJumpIfTrue opCodeType = 5 // Day 05 P2 + opCodeTypeJumpIfFalse opCodeType = 6 // Day 05 P2 + opCodeTypeLessThan opCodeType = 7 // Day 05 P2 + opCodeTypeEquals opCodeType = 8 // Day 05 P2 + opCodeTypeExit opCodeType = 99 // Day 02 +) + +type opCode struct { + Type opCodeType + flags []opCodeFlag +} + +func (o opCode) GetFlag(param int) opCodeFlag { + if param-1 >= len(o.flags) { + return opCodeFlagPosition + } + return o.flags[param-1] +} + +func (o opCode) eq(in opCode) bool { + return o.Type == in.Type && reflect.DeepEqual(o.flags, in.flags) +} + +func parseOpCode(in int) opCode { + out := opCode{} + + out.Type = opCodeType(in % 100) + + var paramFactor = 100 + for { + if in < paramFactor { + break + } + + out.flags = append(out.flags, opCodeFlag((in % (paramFactor * 10) / paramFactor))) + paramFactor *= 10 + } + + return out +} + +func parseIntcode(code string) ([]int, error) { + parts := strings.Split(code, ",") + + var out []int + for _, n := range parts { + v, err := strconv.Atoi(n) + if err != nil { + return nil, err + } + out = append(out, v) + } + + return out, nil +} + +func executeIntcode(code []int, in, out chan int) ([]int, error) { + var pos int + + if out != nil { + defer close(out) + } + + getParamValue := func(param int, op opCode) int { + switch op.GetFlag(param) { + + case opCodeFlagImmediate: + return code[pos+param] + + case opCodeFlagPosition: + return code[code[pos+param]] + + default: + panic(errors.Errorf("Unexpected opCodeFlag %d", op.GetFlag(param))) + + } + } + + for { + if pos >= len(code) { + return nil, errors.Errorf("Code position out of bounds: %d (len=%d)", pos, len(code)) + } + + // Position is expected to be an OpCode + op := parseOpCode(code[pos]) + switch op.Type { + + case opCodeTypeAddition: // p1 + p2 => p3 + code[code[pos+3]] = getParamValue(1, op) + getParamValue(2, op) + pos += 4 + + case opCodeTypeMultiplication: // p1 * p2 => p3 + code[code[pos+3]] = getParamValue(1, op) * getParamValue(2, op) + pos += 4 + + case opCodeTypeInput: // in => p1 + code[code[pos+1]] = <-in + pos += 2 + + case opCodeTypeOutput: // p1 => out + out <- getParamValue(1, op) + pos += 2 + + case opCodeTypeJumpIfTrue: // p1 != 0 => jmp + if getParamValue(1, op) != 0 { + pos = getParamValue(2, op) + continue + } + pos += 3 + + case opCodeTypeJumpIfFalse: // p1 == 0 => jmp + if getParamValue(1, op) == 0 { + pos = getParamValue(2, op) + continue + } + pos += 3 + + case opCodeTypeLessThan: // p1 < p2 => p3 + var res int + if getParamValue(1, op) < getParamValue(2, op) { + res = 1 + } + code[code[pos+3]] = res + pos += 4 + + case opCodeTypeEquals: // p1 == p2 => p3 + var res int + if getParamValue(1, op) == getParamValue(2, op) { + res = 1 + } + code[code[pos+3]] = res + pos += 4 + + case opCodeTypeExit: // exit + return code, nil + + default: + return nil, errors.Errorf("Encountered invalid operation %d (parsed %#v)", code[pos], op) + + } + } +} diff --git a/intcode_test.go b/intcode_test.go new file mode 100644 index 0000000..fc2e4d9 --- /dev/null +++ b/intcode_test.go @@ -0,0 +1,152 @@ +package aoc2019 + +import "testing" + +func TestParseOpCode(t *testing.T) { + for code, expOpCode := range map[int]opCode{ + 1002: {Type: opCodeTypeMultiplication, flags: []opCodeFlag{opCodeFlagPosition, opCodeFlagImmediate}}, + 1101: {Type: opCodeTypeAddition, flags: []opCodeFlag{opCodeFlagImmediate, opCodeFlagImmediate}}, + } { + if op := parseOpCode(code); !op.eq(expOpCode) { + t.Errorf("OpCode execution of code %d yield unexpected result: exp=%+v got=%+v", code, expOpCode, op) + } + } +} + +func TestExecuteIntcodeIO(t *testing.T) { + code, _ := parseIntcode("3,0,4,0,99") + + var ( + exp = 25 + in = make(chan int, 1) + out = make(chan int, 1) + ) + + in <- exp + + if _, err := executeIntcode(code, in, out); err != nil { + t.Fatalf("Intcode execution failed: %s", err) + } + + if r := <-out; r != exp { + t.Errorf("Program yield unexpected result: exp=%d got=%d", exp, r) + } +} + +func TestExecuteIntcodeImmediateFlag(t *testing.T) { + // 102,4,7,0 = Multiply 4 by pos_7, store to pos_0 + // 4,0 = Output pos_0 + // 99 = Exit + // 3 = pos_7 + code, _ := parseIntcode("102,4,7,0,4,0,99,3") + + var ( + exp = 12 + out = make(chan int, 1) + ) + + if _, err := executeIntcode(code, nil, out); err != nil { + t.Fatalf("Intcode execution failed: %s", err) + } + + if r := <-out; r != exp { + t.Errorf("Program yield unexpected result: exp=%d got=%d", exp, r) + } +} + +func TestExecuteIntcodeEquals(t *testing.T) { + for mode, codeStr := range map[string]string{ + "position": "3,9,8,9,10,9,4,9,99,-1,8", + "immediate": "3,3,1108,-1,8,3,4,3,99", + } { + + for input, exp := range map[int]int{ + 1: 0, + 8: 1, + 20: 0, + -8: 0, + } { + var ( + in = make(chan int, 1) + out = make(chan int, 10) + ) + + code, _ := parseIntcode(codeStr) + in <- input + + if _, err := executeIntcode(code, in, out); err != nil { + t.Fatalf("Execute in mode %q with input %d caused an error: %s", mode, input, err) + } + + if r := <-out; r != exp { + t.Errorf("Execute in mode %q with input %d yield unexpected result: exp=%d got=%d", mode, input, exp, r) + } + + } + + } +} + +func TestExecuteIntcodeLessThan(t *testing.T) { + for mode, codeStr := range map[string]string{ + "position": "3,9,7,9,10,9,4,9,99,-1,8", + "immediate": "3,3,1107,-1,8,3,4,3,99", + } { + + for input, exp := range map[int]int{ + 1: 1, + 8: 0, + 20: 0, + -8: 1, + } { + var ( + in = make(chan int, 1) + out = make(chan int, 10) + ) + + code, _ := parseIntcode(codeStr) + in <- input + + if _, err := executeIntcode(code, in, out); err != nil { + t.Fatalf("Execute in mode %q with input %d caused an error: %s", mode, input, err) + } + + if r := <-out; r != exp { + t.Errorf("Execute in mode %q with input %d yield unexpected result: exp=%d got=%d", mode, input, exp, r) + } + + } + + } +} + +func TestExecuteIntcodeJump(t *testing.T) { + for mode, codeStr := range map[string]string{ + "position": "3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9", + "immediate": "3,3,1105,-1,9,1101,0,0,12,4,12,99,1", + } { + + for input, exp := range map[int]int{ + 5: 1, + 0: 0, + } { + var ( + in = make(chan int, 1) + out = make(chan int, 10) + ) + + code, _ := parseIntcode(codeStr) + in <- input + + if _, err := executeIntcode(code, in, out); err != nil { + t.Fatalf("Execute in mode %q with input %d caused an error: %s", mode, input, err) + } + + if r := <-out; r != exp { + t.Errorf("Execute in mode %q with input %d yield unexpected result: exp=%d got=%d", mode, input, exp, r) + } + + } + + } +}