Skip to content

Commit da0855e

Browse files
authored
day16 2020, finally
1 parent fbd0c0d commit da0855e

File tree

4 files changed

+620
-3
lines changed

4 files changed

+620
-3
lines changed

2020/.vscode/launch.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"mode": "auto",
1212
"program": "${workspaceRoot}/main.go",
1313
"env": {},
14-
"args": ["11"]
14+
"args": ["16"]
1515
}
1616
]
1717
}

2020/day16/day16.go

+250-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,255 @@
11
package day16
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
const (
11+
STATE_RULES = iota
12+
STATE_YOUR_TICKET
13+
STATE_NEARBY_TICKETS
14+
)
415

516
func Run(lines []string) error {
6-
return fmt.Errorf("not yet implemented")
17+
rules, yourTicket, nearbyTickets, err := ParseInput(lines)
18+
if err != nil {
19+
return err
20+
}
21+
22+
fmt.Println("Rule count:", len(rules))
23+
24+
errorRate := 0
25+
validTickets := []Ticket{}
26+
for _, ticket := range nearbyTickets {
27+
invalidValues := ticket.GetInvalidValues(rules)
28+
for _, invalidValue := range invalidValues {
29+
errorRate += invalidValue
30+
}
31+
32+
if len(invalidValues) == 0 {
33+
validTickets = append(validTickets, ticket)
34+
}
35+
}
36+
37+
fmt.Println("Part 1:", errorRate)
38+
39+
mappings, err := FindRuleMappings(validTickets, rules)
40+
if err != nil {
41+
return err
42+
}
43+
result := 1
44+
for key, idx := range mappings {
45+
if strings.HasPrefix(key, "departure") {
46+
result *= yourTicket.Values[idx]
47+
}
48+
}
49+
fmt.Println("Part 2:", result)
50+
return nil
51+
}
52+
53+
func ParseInput(input []string) (map[string]Rule, Ticket, []Ticket, error) {
54+
rules := make(map[string]Rule)
55+
var yourTicket Ticket
56+
var nearbyTickets []Ticket
57+
state := STATE_RULES
58+
59+
for _, line := range input {
60+
switch state {
61+
case STATE_RULES:
62+
if len(line) == 0 {
63+
state = STATE_YOUR_TICKET
64+
} else {
65+
rule, err := ParseRule(line)
66+
if err != nil {
67+
return map[string]Rule{}, Ticket{}, []Ticket{}, err
68+
}
69+
rules[rule.name] = rule
70+
}
71+
case STATE_YOUR_TICKET:
72+
if len(line) == 0 {
73+
state = STATE_NEARBY_TICKETS
74+
} else if line != "your ticket:" {
75+
t, err := ParseTicket(line)
76+
if err != nil {
77+
return map[string]Rule{}, Ticket{}, []Ticket{}, err
78+
}
79+
yourTicket = t
80+
}
81+
case STATE_NEARBY_TICKETS:
82+
if line != "nearby tickets:" {
83+
t, err := ParseTicket(line)
84+
if err != nil {
85+
return map[string]Rule{}, Ticket{}, []Ticket{}, err
86+
}
87+
nearbyTickets = append(nearbyTickets, t)
88+
}
89+
}
90+
}
91+
return rules, yourTicket, nearbyTickets, nil
92+
}
93+
94+
type Ticket struct {
95+
Values []int
96+
}
97+
98+
func ParseTicket(line string) (Ticket, error) {
99+
splat := strings.Split(line, ",")
100+
vals := make([]int, 0, len(splat))
101+
for _, s := range splat {
102+
v, err := strconv.Atoi(s)
103+
if err != nil {
104+
return Ticket{}, fmt.Errorf("invalid value '%s'", s)
105+
}
106+
vals = append(vals, v)
107+
}
108+
return Ticket{vals}, nil
109+
}
110+
111+
func (t *Ticket) GetInvalidValues(rules map[string]Rule) []int {
112+
invalid := []int{}
113+
for _, val := range t.Values {
114+
validForRule := false
115+
for _, r := range rules {
116+
if r.Matches(val) {
117+
validForRule = true
118+
}
119+
}
120+
if !validForRule {
121+
invalid = append(invalid, val)
122+
}
123+
}
124+
return invalid
125+
}
126+
127+
func ValidRulesAtIndex(tickets []Ticket, index int, rules map[string]Rule) []string {
128+
validRules := []string{}
129+
for _, rule := range rules {
130+
allValid := true
131+
for _, ticket := range tickets {
132+
if !rule.Matches(ticket.Values[index]) {
133+
allValid = false
134+
break
135+
}
136+
}
137+
if allValid {
138+
validRules = append(validRules, rule.name)
139+
}
140+
}
141+
return validRules
142+
}
143+
144+
func FindAllValidRules(tickets []Ticket, rules map[string]Rule) [][]string {
145+
validRules := [][]string{}
146+
for i := 0; i < len(tickets[0].Values); i++ {
147+
validRules = append(validRules, ValidRulesAtIndex(tickets, i, rules))
148+
}
149+
return validRules
150+
}
151+
152+
func FindRuleMappings(tickets []Ticket, rules map[string]Rule) (map[string]int, error) {
153+
validRulesByIndex := FindAllValidRules(tickets, rules)
154+
mappings := map[string]int{}
155+
for cycle := 0; len(mappings) < len(rules); cycle++ {
156+
promoted := false
157+
// Check for any columns with only one valid rule
158+
for idx, validRules := range validRulesByIndex {
159+
if len(validRules) == 1 {
160+
promoted = true
161+
fmt.Printf("Promoted: '%s' => %d\n", validRules[0], idx)
162+
mappings[validRules[0]] = idx
163+
}
164+
}
165+
166+
if !promoted {
167+
return nil, fmt.Errorf("made no progress")
168+
}
169+
170+
// Remove the now-mapped rule from the valid set
171+
newValidRulesByIndex := [][]string{}
172+
for _, validRules := range validRulesByIndex {
173+
newValidRules := []string{}
174+
for _, currentRule := range validRules {
175+
if _, ok := mappings[currentRule]; !ok {
176+
newValidRules = append(newValidRules, currentRule)
177+
}
178+
}
179+
newValidRulesByIndex = append(newValidRulesByIndex, newValidRules)
180+
}
181+
validRulesByIndex = newValidRulesByIndex
182+
}
183+
return mappings, nil
184+
}
185+
186+
type Range struct {
187+
Min int
188+
Max int
189+
}
190+
191+
func (r *Range) Includes(num int) bool {
192+
return num >= r.Min && num <= r.Max
193+
}
194+
195+
func ParseRange(input string) (Range, error) {
196+
splat := strings.Split(input, "-")
197+
if len(splat) != 2 {
198+
return Range{}, fmt.Errorf("invalid range '%s'", input)
199+
}
200+
201+
min, err := strconv.Atoi(splat[0])
202+
if err != nil {
203+
return Range{}, fmt.Errorf("invalid minimum '%s'", splat[0])
204+
}
205+
max, err := strconv.Atoi(splat[1])
206+
if err != nil {
207+
return Range{}, fmt.Errorf("invalid maximum '%s'", splat[0])
208+
}
209+
return Range{min, max}, nil
210+
}
211+
212+
type Rule struct {
213+
name string
214+
ranges []Range
215+
}
216+
217+
var ruleMatcher = regexp.MustCompile(`^([^:]*): (\d+-\d+) or (\d+-\d+)$`)
218+
219+
func ParseRules(input []string) ([]Rule, error) {
220+
rules := make([]Rule, 0, len(input))
221+
for _, line := range input {
222+
rule, err := ParseRule(line)
223+
if err != nil {
224+
return nil, err
225+
}
226+
rules = append(rules, rule)
227+
}
228+
return rules, nil
229+
}
230+
231+
func ParseRule(input string) (Rule, error) {
232+
subexprs := ruleMatcher.FindStringSubmatch(input)
233+
if len(subexprs) != 4 {
234+
return Rule{}, fmt.Errorf("invalid rule '%s'", input)
235+
}
236+
name := strings.TrimSpace(subexprs[1])
237+
ranges := make([]Range, 0, len(subexprs)-2)
238+
for _, s := range subexprs[2:] {
239+
rg, err := ParseRange(s)
240+
if err != nil {
241+
return Rule{}, fmt.Errorf("invalid range '%s': %v", s, err)
242+
}
243+
ranges = append(ranges, rg)
244+
}
245+
return Rule{name, ranges}, nil
246+
}
247+
248+
func (r *Rule) Matches(num int) bool {
249+
for _, rg := range r.ranges {
250+
if rg.Includes(num) {
251+
return true
252+
}
253+
}
254+
return false
7255
}

2020/day16/day16_test.go

+101
Original file line numberDiff line numberDiff line change
@@ -1 +1,102 @@
11
package day16
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestRuleParser(t *testing.T) {
10+
input := []string{
11+
"class: 1-3 or 5-7",
12+
"row: 6-11 or 33-44",
13+
"seat: 13-40 or 45-50",
14+
}
15+
16+
rules, err := ParseRules(input)
17+
require.NoError(t, err)
18+
require.Equal(t, []Rule{
19+
{"class", []Range{{1, 3}, {5, 7}}},
20+
{"row", []Range{{6, 11}, {33, 44}}},
21+
{"seat", []Range{{13, 40}, {45, 50}}},
22+
}, rules)
23+
}
24+
25+
func TestTicketParser(t *testing.T) {
26+
ticket, err := ParseTicket("7,1,14")
27+
require.NoError(t, err)
28+
require.Equal(t, []int{7, 1, 14}, ticket.Values)
29+
}
30+
31+
var testRules = map[string]Rule{
32+
"class": {"class", []Range{{1, 3}, {5, 7}}},
33+
"row": {"row", []Range{{6, 11}, {33, 44}}},
34+
"seat": {"seat", []Range{{13, 40}, {45, 50}}},
35+
}
36+
37+
var testYourTicket = Ticket{[]int{7, 1, 14}}
38+
39+
var testNearbyTickets = []Ticket{
40+
{[]int{7, 3, 47}},
41+
{[]int{40, 4, 50}},
42+
{[]int{55, 2, 20}},
43+
{[]int{38, 6, 12}},
44+
}
45+
46+
func TestFullParse(t *testing.T) {
47+
input := []string{
48+
"class: 1-3 or 5-7",
49+
"row: 6-11 or 33-44",
50+
"seat: 13-40 or 45-50",
51+
"",
52+
"your ticket:",
53+
"7,1,14",
54+
"",
55+
"nearby tickets:",
56+
"7,3,47",
57+
"40,4,50",
58+
"55,2,20",
59+
"38,6,12",
60+
}
61+
62+
rules, yourTicket, nearbyTickets, err := ParseInput(input)
63+
require.NoError(t, err)
64+
require.Equal(t, testRules, rules)
65+
require.Equal(t, testYourTicket, yourTicket)
66+
require.Equal(t, testNearbyTickets, nearbyTickets)
67+
}
68+
69+
func TestFindInvalidTickets(t *testing.T) {
70+
require.Equal(t, []int{}, testNearbyTickets[0].GetInvalidValues(testRules))
71+
require.Equal(t, []int{4}, testNearbyTickets[1].GetInvalidValues(testRules))
72+
require.Equal(t, []int{55}, testNearbyTickets[2].GetInvalidValues(testRules))
73+
require.Equal(t, []int{12}, testNearbyTickets[3].GetInvalidValues(testRules))
74+
}
75+
76+
func TestValidRulesAtIndex(t *testing.T) {
77+
testRules := map[string]Rule{
78+
"class": {"class", []Range{{0, 1}, {4, 16}}},
79+
"row": {"row", []Range{{0, 5}, {8, 19}}},
80+
"seat": {"seat", []Range{{0, 13}, {16, 19}}},
81+
}
82+
testTickets := []Ticket{
83+
{[]int{3, 9, 18}},
84+
{[]int{15, 1, 5}},
85+
{[]int{5, 14, 9}},
86+
}
87+
expectedRuleValidity := [][]string{
88+
{"row"},
89+
{"class", "row"},
90+
{"seat", "row"},
91+
}
92+
93+
actual := FindAllValidRules(testTickets, testRules)
94+
95+
for i := range actual {
96+
require.ElementsMatch(t, expectedRuleValidity[i], actual[i])
97+
}
98+
99+
mappings, err := FindRuleMappings(testTickets, testRules)
100+
require.NoError(t, err)
101+
require.Equal(t, map[string]int{"row": 0, "class": 1, "seat": 2}, mappings)
102+
}

0 commit comments

Comments
 (0)