Skip to content

Commit 773b8db

Browse files
authored
day 18 2020
1 parent 4a9162b commit 773b8db

File tree

3 files changed

+642
-2
lines changed

3 files changed

+642
-2
lines changed

2020/day18/day18.go

+173-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,178 @@
11
package day18
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
"text/scanner"
8+
)
49

510
func Run(lines []string) error {
6-
return fmt.Errorf("not yet implemented")
11+
sum, err := SumAll(lines, EqualPrecedence)
12+
if err != nil {
13+
return err
14+
}
15+
fmt.Println("Part 1:", sum)
16+
sum, err = SumAll(lines, AdvancedPrecedence)
17+
if err != nil {
18+
return err
19+
}
20+
fmt.Println("Part 2:", sum)
21+
return nil
22+
}
23+
24+
func SumAll(input []string, prec Precedences) (int, error) {
25+
sum := 0
26+
for _, line := range input {
27+
val, err := Evaluate(line, prec)
28+
if err != nil {
29+
return 0, err
30+
}
31+
sum += val
32+
}
33+
return sum, nil
34+
}
35+
36+
func Evaluate(input string, prec Precedences) (int, error) {
37+
tokens := tokenize(input)
38+
tokens = postfixize(tokens, prec)
39+
res, err := evaluatePostfixed(tokens)
40+
if err != nil {
41+
return 0, err
42+
}
43+
return res, nil
44+
}
45+
46+
func evaluatePostfixed(input []string) (int, error) {
47+
stack := []int{}
48+
for _, token := range input {
49+
switch token {
50+
case "+", "*":
51+
if len(stack) < 2 {
52+
return 0, fmt.Errorf("stack underflow")
53+
}
54+
x, y := stack[len(stack)-2], stack[len(stack)-1]
55+
stack = stack[:len(stack)-2]
56+
res, err := execute(token, x, y)
57+
if err != nil {
58+
return 0, err
59+
}
60+
stack = append(stack, res)
61+
default:
62+
num, err := strconv.Atoi(token)
63+
if err != nil {
64+
return 0, fmt.Errorf("invalid number '%s'", token)
65+
}
66+
stack = append(stack, num)
67+
}
68+
}
69+
70+
if len(stack) != 1 {
71+
return 0, fmt.Errorf("after evaluating %v, stack should have been at 1 item: %v", input, stack)
72+
}
73+
return stack[0], nil
74+
}
75+
76+
func execute(op string, x, y int) (int, error) {
77+
if op == "+" {
78+
return x + y, nil
79+
} else if op == "*" {
80+
return x * y, nil
81+
} else {
82+
return 0, fmt.Errorf("unknown operator '%s'", op)
83+
}
84+
}
85+
86+
func tokenize(input string) []string {
87+
tokens := []string{}
88+
s := scanner.Scanner{}
89+
s.Init(strings.NewReader(input))
90+
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
91+
tokens = append(tokens, s.TokenText())
92+
}
93+
return tokens
94+
}
95+
96+
type Precedences map[string]int
97+
98+
var EqualPrecedence = Precedences{"+": 1, "*": 1}
99+
var AdvancedPrecedence = Precedences{"+": 2, "*": 1}
100+
101+
func (p *Precedences) PrecedenceOf(input string) int {
102+
if prec, ok := (*p)[input]; ok {
103+
return prec
104+
}
105+
return 0
106+
}
107+
108+
func postfixize(input []string, prec Precedences) []string {
109+
output := []string{}
110+
operators := []string{}
111+
for _, token := range input {
112+
switch token {
113+
case "(":
114+
operators = push(operators, token)
115+
case ")":
116+
popUntil(&operators, &output, func(x string) bool { return x == "(" })
117+
118+
// do pop the "("
119+
var popped string
120+
operators, popped = pop(operators)
121+
if popped != "(" {
122+
panic("parens out of balance")
123+
}
124+
case "+", "*":
125+
if prec.PrecedenceOf(token) > prec.PrecedenceOf(top(operators)) {
126+
operators = push(operators, token)
127+
} else {
128+
for top(operators) != "" && prec.PrecedenceOf(token) <= prec.PrecedenceOf(top(operators)) {
129+
popTo(&operators, &output)
130+
}
131+
operators = push(operators, token)
132+
}
133+
default:
134+
output = push(output, token)
135+
}
136+
}
137+
138+
// Pop remaining operators to the output
139+
popUntil(&operators, &output, func(_ string) bool { return false })
140+
return output
141+
}
142+
143+
func popTo(source *[]string, dest *[]string) {
144+
newSrc, popped := pop(*source)
145+
*dest = push(*dest, popped)
146+
*source = newSrc
147+
}
148+
149+
func popUntil(operators *[]string, output *[]string, pred func(string) bool) {
150+
// Pop operators until '('
151+
var popped string
152+
for {
153+
t := top(*operators)
154+
if t == "" || pred(t) {
155+
break
156+
}
157+
*operators, popped = pop(*operators)
158+
*output = push(*output, popped)
159+
}
160+
}
161+
162+
func push(stack []string, value string) []string {
163+
return append(stack, value)
164+
}
165+
166+
func pop(stack []string) ([]string, string) {
167+
if len(stack) == 0 {
168+
return nil, ""
169+
}
170+
return stack[0 : len(stack)-1], stack[len(stack)-1]
171+
}
172+
173+
func top(stack []string) string {
174+
if len(stack) == 0 {
175+
return ""
176+
}
177+
return stack[len(stack)-1]
7178
}

2020/day18/day18_test.go

+90
Original file line numberDiff line numberDiff line change
@@ -1 +1,91 @@
11
package day18
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestPushPop(t *testing.T) {
10+
stack := []string{}
11+
stack = push(stack, "a")
12+
stack = push(stack, "b")
13+
stack = push(stack, "c")
14+
require.Equal(t, []string{"a", "b", "c"}, stack)
15+
16+
var res string
17+
stack, res = pop(stack)
18+
require.Equal(t, []string{"a", "b"}, stack)
19+
require.Equal(t, "c", res)
20+
21+
stack, res = pop(stack)
22+
require.Equal(t, []string{"a"}, stack)
23+
require.Equal(t, "b", res)
24+
25+
stack, res = pop(stack)
26+
require.Equal(t, []string{}, stack)
27+
require.Equal(t, "a", res)
28+
29+
stack, res = pop(stack)
30+
require.Nil(t, stack)
31+
require.Equal(t, "", res)
32+
}
33+
34+
func TestTokenize(t *testing.T) {
35+
input := "((1 + 23) * 42)"
36+
tokens := tokenize(input)
37+
require.Equal(t, []string{
38+
"(", "(", "1", "+", "23", ")", "*", "42", ")",
39+
}, tokens)
40+
}
41+
42+
func TestPostfixize(t *testing.T) {
43+
input := []string{
44+
"(", "(", "1", "+", "23", ")", "*", "42", ")",
45+
}
46+
expectedOutput := []string{
47+
"1", "23", "+", "42", "*",
48+
}
49+
require.Equal(t, expectedOutput, postfixize(input, EqualPrecedence))
50+
51+
input = []string{
52+
"1", "+", "2", "*", "3", "+", "4", "*", "5", "+", "6",
53+
}
54+
expectedOutput = []string{
55+
"1", "2", "+", "3", "*", "4", "+", "5", "*", "6", "+",
56+
}
57+
require.Equal(t, expectedOutput, postfixize(input, EqualPrecedence))
58+
}
59+
60+
func TestEvaluatePostfixed(t *testing.T) {
61+
tokens := []string{
62+
"1", "23", "+", "42", "*",
63+
}
64+
res, err := evaluatePostfixed(tokens)
65+
require.NoError(t, err)
66+
require.Equal(t, 1008, res)
67+
}
68+
69+
func TestEvaluate(t *testing.T) {
70+
require.Equal(t, 71, mustEvaluate(t, "1 + 2 * 3 + 4 * 5 + 6", EqualPrecedence))
71+
require.Equal(t, 51, mustEvaluate(t, "1 + (2 * 3) + (4 * (5 + 6))", EqualPrecedence))
72+
require.Equal(t, 26, mustEvaluate(t, "2 * 3 + (4 * 5)", EqualPrecedence))
73+
require.Equal(t, 437, mustEvaluate(t, "5 + (8 * 3 + 9 + 3 * 4 * 3)", EqualPrecedence))
74+
require.Equal(t, 12240, mustEvaluate(t, "5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))", EqualPrecedence))
75+
require.Equal(t, 13632, mustEvaluate(t, "((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2", EqualPrecedence))
76+
}
77+
78+
func TestEvaluateAdvanced(t *testing.T) {
79+
require.Equal(t, 231, mustEvaluate(t, "1 + 2 * 3 + 4 * 5 + 6", AdvancedPrecedence))
80+
require.Equal(t, 51, mustEvaluate(t, "1 + (2 * 3) + (4 * (5 + 6))", AdvancedPrecedence))
81+
require.Equal(t, 46, mustEvaluate(t, "2 * 3 + (4 * 5)", AdvancedPrecedence))
82+
require.Equal(t, 1445, mustEvaluate(t, "5 + (8 * 3 + 9 + 3 * 4 * 3)", AdvancedPrecedence))
83+
require.Equal(t, 669060, mustEvaluate(t, "5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))", AdvancedPrecedence))
84+
require.Equal(t, 23340, mustEvaluate(t, "((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2", AdvancedPrecedence))
85+
}
86+
87+
func mustEvaluate(t *testing.T, input string, prec Precedences) int {
88+
res, err := Evaluate(input, prec)
89+
require.NoError(t, err)
90+
return res
91+
}

0 commit comments

Comments
 (0)