Skip to content
This repository was archived by the owner on Dec 28, 2024. It is now read-only.

Commit 6031974

Browse files
committed
Add solution day 17 part 1
1 parent fd4415d commit 6031974

File tree

6 files changed

+335
-2
lines changed

6 files changed

+335
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ automatically rebuilt and redeployed every time the `common` module or their own
4949
| 01 | ⭐ ⭐ | 14 | ⭐ ⭐ |
5050
| 02 | ⭐ ⭐ | 15 | ⭐ ⭐ |
5151
| 03 | ⭐ ⭐ | 16 | ⭐ 🥸 |
52-
| 04 | ⭐ ⭐ | 17 | |
52+
| 04 | ⭐ ⭐ | 17 | |
5353
| 05 | ⭐ ⭐ | 18 | |
5454
| 06 | ⭐ ⭐ | 19 | |
5555
| 07 | ⭐ ⭐ | 20 | |

solutions/day17/machine.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
)
7+
8+
type machine struct {
9+
a int64
10+
b int64
11+
c int64
12+
seq []int64
13+
out []int64
14+
pos int
15+
count int
16+
}
17+
18+
func (m *machine) run(maxItt int) {
19+
for m.pos < len(m.seq) && (maxItt <= 0 || m.count < maxItt) {
20+
m.next()
21+
m.count++
22+
}
23+
}
24+
25+
func (m *machine) next() {
26+
switch m.seq[m.pos] {
27+
// Division operators
28+
case 0:
29+
// log.Println("ADV")
30+
m.a = m.a / (1 << m.comboOperand())
31+
m.incrementPos(2)
32+
case 6:
33+
// log.Println("BDV")
34+
m.b = m.a / (1 << m.comboOperand())
35+
m.incrementPos(2)
36+
case 7:
37+
// log.Println("CDV")
38+
m.c = m.a / (1 << m.comboOperand())
39+
m.incrementPos(2)
40+
41+
// Bitwise operators
42+
case 1:
43+
// log.Println("BXL")
44+
m.b = m.b ^ m.literalOperand()
45+
m.incrementPos(2)
46+
case 4:
47+
// log.Println("BXC")
48+
m.b = m.b ^ m.c
49+
m.incrementPos(2)
50+
51+
// Modulo operators
52+
case 5:
53+
// log.Println("OUT")
54+
m.out = append(m.out, m.comboOperand()%8)
55+
m.incrementPos(2)
56+
case 2:
57+
// log.Println("BST")
58+
m.b = m.comboOperand() % 8
59+
m.incrementPos(2)
60+
61+
// Other
62+
case 3:
63+
// log.Println("JNZ")
64+
if m.a == 0 {
65+
m.incrementPos(2)
66+
} else {
67+
m.pos = int(m.seq[m.pos+1])
68+
}
69+
}
70+
}
71+
72+
func (m *machine) incrementPos(n int) {
73+
m.pos += n
74+
}
75+
76+
func (m *machine) comboOperand() int64 {
77+
raw := m.literalOperand()
78+
switch {
79+
case raw < 0:
80+
panic("operand can not be < 0")
81+
case raw <= 3:
82+
return raw
83+
case raw == 4:
84+
return m.a
85+
case raw == 5:
86+
return m.b
87+
case raw == 6:
88+
return m.c
89+
default:
90+
log.Println(m)
91+
panic("failed to get operand (can not be >= 7)")
92+
}
93+
}
94+
95+
func (m *machine) literalOperand() int64 {
96+
return m.seq[m.pos+1]
97+
}
98+
99+
func (m *machine) strOut() string {
100+
if len(m.out) == 0 {
101+
return ""
102+
}
103+
104+
s := fmt.Sprintf("%d", m.out[0])
105+
for _, o := range m.out[1:] {
106+
s += fmt.Sprintf(",%d", o)
107+
}
108+
return s
109+
}
110+
111+
func (m *machine) String() string {
112+
curr := "out of bounds"
113+
if m.pos > 0 && m.pos < len(m.seq) {
114+
curr = fmt.Sprintf("%d", m.seq[m.pos])
115+
}
116+
return fmt.Sprintf("machine{a:%d, b:%d, c:%d, seq:%v, out:%v, pos:%d (%s)}", m.a, m.b, m.c, m.seq, m.out, m.pos, curr)
117+
}

solutions/day17/machine_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"testing"
7+
)
8+
9+
func TestMachineRun(t *testing.T) {
10+
tests := []struct {
11+
n string
12+
m machine
13+
f func(machine) bool
14+
}{
15+
{
16+
n: "#1 If register C contains 9, the program 2,6 would set register B to 1.",
17+
m: machine{c: 9, seq: []int64{2, 6}},
18+
f: func(m machine) bool { return m.b == 1 },
19+
}, {
20+
n: "#2 If register A contains 10, the program 5,0,5,1,5,4 would output 0,1,2.",
21+
m: machine{a: 10, seq: []int64{5, 0, 5, 1, 5, 4}},
22+
f: func(m machine) bool { return m.strOut() == "0,1,2" },
23+
}, {
24+
n: "#3 If register A contains 2024, the program 0,1,5,4,3,0 would output 4,2,5,6,7,7,7,7,3,1,0 and leave 0 in register A.",
25+
m: machine{a: 2024, seq: []int64{0, 1, 5, 4, 3, 0}},
26+
f: func(m machine) bool {
27+
return m.a == 0 && m.strOut() == "4,2,5,6,7,7,7,7,3,1,0"
28+
},
29+
}, {
30+
n: "#4 If register B contains 29, the program 1,7 would set register B to 26.",
31+
m: machine{b: 29, seq: []int64{1, 7}},
32+
f: func(m machine) bool { return m.b == 26 },
33+
}, {
34+
n: "#5 If register B contains 2024 and register C contains 43690, the program 4,0 would set register B to 44354.",
35+
m: machine{b: 2024, c: 43690, seq: []int64{4, 0}},
36+
f: func(m machine) bool { return m.b == 44354 },
37+
},
38+
}
39+
40+
for _, test := range tests {
41+
t.Run(test.n, func(t *testing.T) {
42+
test.m.run(100)
43+
if !test.f(test.m) {
44+
t.Errorf("Test validation failed with %s", test.m.String())
45+
fmt.Println(test.m)
46+
}
47+
})
48+
}
49+
}
50+
51+
func TestMachine_String(t *testing.T) {
52+
min64I := int64(math.MinInt64) // -9223372036854775808
53+
max64I := int64(math.MaxInt64) // 9223372036854775807
54+
55+
tests := []struct {
56+
n string
57+
m machine
58+
e string
59+
}{
60+
{
61+
n: "machine with boundary int64 values",
62+
m: machine{a: min64I, b: 0, c: max64I, seq: []int64{min64I, 0, max64I}, pos: 2},
63+
e: "machine{a:-9223372036854775808, b:0, c:9223372036854775807, seq:[-9223372036854775808 0 9223372036854775807], out:[], pos:2 (9223372036854775807)}",
64+
},
65+
{
66+
n: "machine with positive out of bounds index",
67+
m: machine{a: 0, b: 1, c: 2, seq: []int64{1, 2, 3, 4, 5}, out: []int64{1, 2, 3}, pos: 5},
68+
e: "machine{a:0, b:1, c:2, seq:[1 2 3 4 5], out:[1 2 3], pos:5 (out of bounds)}",
69+
},
70+
{
71+
n: "machine with negative out of bounds index",
72+
m: machine{a: 0, b: 1, c: 2, seq: []int64{1, 2, 3, 4, 5}, pos: -1},
73+
e: "machine{a:0, b:1, c:2, seq:[1 2 3 4 5], out:[], pos:-1 (out of bounds)}",
74+
},
75+
}
76+
77+
for _, test := range tests {
78+
t.Run(test.n, func(t *testing.T) {
79+
actual := test.m.String()
80+
if actual != test.e {
81+
t.Errorf("expected: '%s'\nbut got: '%s'", test.e, actual)
82+
}
83+
})
84+
}
85+
}

solutions/day17/main.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
package main
22

33
import (
4+
"fmt"
45
"github.com/terminalnode/adventofcode2024/common"
56
)
67

78
func main() {
8-
common.Setup(17, nil, nil)
9+
common.Setup(17, part1, nil)
10+
}
11+
12+
func part1(
13+
input string,
14+
) string {
15+
m, err := parseMachine(input)
16+
if err != nil {
17+
return fmt.Sprintf("Failed to parse machine: %v", err)
18+
}
19+
m.run(-1)
20+
return m.strOut()
921
}

solutions/day17/main_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
const ex1Out = "4,6,3,5,6,3,5,2,1,0"
8+
const ex1 = `Register A: 729
9+
Register B: 0
10+
Register C: 0
11+
12+
Program: 0,1,5,4,3,0`
13+
14+
func TestParseMachine(t *testing.T) {
15+
testInput := `Register A: -1337
16+
Register B: 0
17+
Register C: 666
18+
19+
Program: 0,-1,5,-4,3,0`
20+
testProgram := []int64{0, -1, 5, -4, 3, 0}
21+
22+
m, err := parseMachine(testInput)
23+
24+
if err != nil {
25+
t.Errorf("Failed to parse machine: %v", err)
26+
}
27+
28+
if m.a != -1337 {
29+
t.Errorf("expected m.a == -1337, but was %d", m.c)
30+
}
31+
32+
if m.b != 0 {
33+
t.Errorf("expected m.b == 0, but was %d", m.c)
34+
}
35+
36+
if m.c != 666 {
37+
t.Errorf("expected m.c == 666, but was %d", m.c)
38+
}
39+
40+
if len(m.seq) != len(testProgram) {
41+
t.Errorf("Expected len(m.seq) == %d, but was %d", len(testProgram), len(m.seq))
42+
}
43+
44+
for i, e := range testProgram {
45+
a := m.seq[i]
46+
if e != a {
47+
t.Errorf("Expected m.seq[%d] == %d, but was %d", i, e, a)
48+
}
49+
}
50+
}
51+
52+
func TestPart1_Example1(t *testing.T) {
53+
out := part1(ex1)
54+
if out != ex1Out {
55+
t.Errorf("expected '%s' but got '%s'", ex1Out, out)
56+
}
57+
}

solutions/day17/parse.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
var r = regexp.MustCompile(`Register A: (-?\d+)\nRegister B: (-?\d+)\nRegister C: (-?\d+)\n\nProgram: ((?:-?\d,)+)`)
11+
12+
func parseMachine(
13+
input string,
14+
) (machine, error) {
15+
out := machine{}
16+
matches := r.FindStringSubmatch(input)
17+
if len(matches) != 5 {
18+
return out, fmt.Errorf("expected four fields but got %d", len(matches))
19+
}
20+
21+
digits, err := parseRegisters(matches)
22+
if err != nil {
23+
return out, err
24+
}
25+
out.a = digits[0]
26+
out.b = digits[1]
27+
out.c = digits[2]
28+
29+
seq, err := parseProgram(matches[4])
30+
out.seq = seq
31+
32+
return out, nil
33+
}
34+
35+
func parseRegisters(
36+
matches []string,
37+
) ([]int64, error) {
38+
out := make([]int64, 3)
39+
for i, m := range matches[1:4] {
40+
mAsI64, err := strconv.ParseInt(m, 10, 64)
41+
if err != nil {
42+
return out, err
43+
}
44+
out[i] = mAsI64
45+
}
46+
return out, nil
47+
}
48+
49+
func parseProgram(
50+
program string,
51+
) ([]int64, error) {
52+
parts := strings.Split(program, ",")
53+
out := make([]int64, len(parts))
54+
for i, part := range parts {
55+
partAsI64, err := strconv.ParseInt(part, 10, 64)
56+
if err != nil {
57+
return out, err
58+
}
59+
out[i] = partAsI64
60+
}
61+
return out, nil
62+
}

0 commit comments

Comments
 (0)