diff --git a/day07.go b/day07.go new file mode 100644 index 0000000..8d3e149 --- /dev/null +++ b/day07.go @@ -0,0 +1,102 @@ +package aoc2019 + +import ( + "io/ioutil" + "strings" + + "github.com/pkg/errors" +) + +func day07TestMaxOutputFromChain(code []int, chainStart, chainEnd int, looped bool) int { + var ( + chainLen = chainEnd - chainStart + 1 + permuts [][]int + permute func(a []int, k int) + rootSeq = make([]int, chainLen) + ) + + permute = func(a []int, k int) { + if k == len(a) { + permuts = append(permuts, append([]int{}, a...)) + } else { + for i := k; i < len(rootSeq); i++ { + a[k], a[i] = a[i], a[k] + permute(a, k+1) + a[k], a[i] = a[i], a[k] + } + } + } + + for i := range rootSeq { + rootSeq[i] = chainStart + i + } + permute(rootSeq, 0) + + var maxOutput int + for _, seq := range permuts { + var ( + commInChans = make([]chan int, chainLen) + commOut = make(chan int, 2) + ) + + // Create channels + for i := range commInChans { + commInChans[i] = make(chan int, 2) + } + + // Build execution chain + for i := 0; i < chainLen-1; i++ { + go executeIntcode(cloneIntcode(code), commInChans[i], commInChans[i+1]) + } + go executeIntcode(cloneIntcode(code), commInChans[chainLen-1], commOut) + + // Initialize chain + for i, v := range seq { + commInChans[i] <- v + } + commInChans[0] <- 0 // Input signal + + var lastOutput int + for r := range commOut { + lastOutput = r + if looped { + commInChans[0] <- r + } + } + + // Test output of last execution + if lastOutput > maxOutput { + maxOutput = lastOutput + } + } + + return maxOutput +} + +func solveDay7Part1(inFile string) (int, error) { + raw, err := ioutil.ReadFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read input file") + } + + code, err := parseIntcode(strings.TrimSpace(string(raw))) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse intcode program") + } + + return day07TestMaxOutputFromChain(code, 0, 4, false), nil +} + +func solveDay7Part2(inFile string) (int, error) { + raw, err := ioutil.ReadFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read input file") + } + + code, err := parseIntcode(strings.TrimSpace(string(raw))) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse intcode program") + } + + return day07TestMaxOutputFromChain(code, 5, 9, true), nil +} diff --git a/day07_input.txt b/day07_input.txt new file mode 100644 index 0000000..98129d6 --- /dev/null +++ b/day07_input.txt @@ -0,0 +1 @@ +3,8,1001,8,10,8,105,1,0,0,21,46,59,84,93,102,183,264,345,426,99999,3,9,1002,9,4,9,1001,9,3,9,102,2,9,9,1001,9,5,9,102,3,9,9,4,9,99,3,9,1002,9,3,9,101,4,9,9,4,9,99,3,9,1002,9,4,9,1001,9,4,9,102,2,9,9,1001,9,2,9,1002,9,3,9,4,9,99,3,9,1001,9,5,9,4,9,99,3,9,1002,9,4,9,4,9,99,3,9,101,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,99,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,99,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,99 diff --git a/day07_test.go b/day07_test.go new file mode 100644 index 0000000..a11b84b --- /dev/null +++ b/day07_test.go @@ -0,0 +1,91 @@ +package aoc2019 + +import "testing" + +func TestChainedInput(t *testing.T) { + code, err := parseIntcode("3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0") + if err != nil { + t.Fatalf("Intcode parser failed: %s", err) + } + + // Create channels + var ( + aIn = make(chan int, 2) + bIn = make(chan int, 2) + cIn = make(chan int, 2) + dIn = make(chan int, 2) + eIn = make(chan int, 2) + eOut = make(chan int, 2) + ) + + // Build execution chain + go executeIntcode(cloneIntcode(code), aIn, bIn) + go executeIntcode(cloneIntcode(code), bIn, cIn) + go executeIntcode(cloneIntcode(code), cIn, dIn) + go executeIntcode(cloneIntcode(code), dIn, eIn) + go executeIntcode(cloneIntcode(code), eIn, eOut) + + // Initialize chain + aIn <- 4 // Sequence + aIn <- 0 // Input signal + bIn <- 3 // Sequence + cIn <- 2 // Sequence + dIn <- 1 // Sequence + eIn <- 0 // Sequence + + // Test output of last execution + if r := <-eOut; r != 43210 { + t.Errorf("Unexpected result from chain: exp=43210 got=%d", r) + } +} + +func TestMaxOutputFromChain(t *testing.T) { + for codeStr, expValue := range map[string]int{ + "3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0": 43210, + "3,23,3,24,1002,24,10,24,1002,23,-1,23,101,5,23,23,1,24,23,23,4,23,99,0,0": 54321, + "3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33,1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0": 65210, + } { + code, err := parseIntcode(codeStr) + if err != nil { + t.Fatalf("Parsing Intcode failed: %s", err) + } + + if r := day07TestMaxOutputFromChain(code, 0, 4, false); r != expValue { + t.Errorf("Max output yield unexpected result: exp=%d got=%d", expValue, r) + } + } +} + +func TestMaxOutputFromLoopedChain(t *testing.T) { + for codeStr, expValue := range map[string]int{ + "3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5": 139629729, + "3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10": 18216, + } { + code, err := parseIntcode(codeStr) + if err != nil { + t.Fatalf("Parsing Intcode failed: %s", err) + } + + if r := day07TestMaxOutputFromChain(code, 5, 9, true); r != expValue { + t.Errorf("Max output yield unexpected result: exp=%d got=%d", expValue, r) + } + } +} + +func TestCalculateDay7_Part1(t *testing.T) { + codeP0, err := solveDay7Part1("day07_input.txt") + if err != nil { + t.Fatalf("Day 7 solver failed: %s", err) + } + + t.Logf("Solution Day 7 Part 1: %d", codeP0) +} + +func TestCalculateDay7_Part2(t *testing.T) { + result, err := solveDay7Part2("day07_input.txt") + if err != nil { + t.Fatalf("Day 7 solver failed: %s", err) + } + + t.Logf("Solution Day 7 Part 2: %d", result) +} diff --git a/intcode.go b/intcode.go index 5082e49..90ba200 100644 --- a/intcode.go +++ b/intcode.go @@ -63,6 +63,14 @@ func parseOpCode(in int) opCode { return out } +func cloneIntcode(in []int) []int { + out := make([]int, len(in)) + for i, v := range in { + out[i] = v + } + return out +} + func parseIntcode(code string) ([]int, error) { parts := strings.Split(code, ",")