-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path16.koto
126 lines (106 loc) · 3.48 KB
/
16.koto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# https://adventofcode.com/2020/day/16
from io import extend_path, read_to_string
from koto import script_dir
parse_notes = |input|
fields = {}
lines = input.lines()
for line in lines
if line.is_empty() then break
# e.g.
# departure location: 32-615 or 626-955
sections = line.split ": "
category = sections.next()
rules = sections.next().split(" ")
.keep |word| word != "or"
.each |word|
bounds = word.split("-")
min = bounds.next().to_number()
max = bounds.next().to_number()
min..=max
.to_tuple()
fields.insert category, {rules}
parse_ticket = |ticket|
ticket
.split ","
.each |n| n.to_number()
.to_tuple()
lines.next() # your ticket:
your_ticket = parse_ticket lines.next()
lines.next() #
lines.next() # nearby tickets:
nearby_tickets = lines.each(parse_ticket).to_tuple()
fields, your_ticket, nearby_tickets
find_invalid_field = |ticket, fields|
for n in ticket
is_valid = fields.any |(_, field)|
field.rules
.any |rule| rule.contains n
if not is_valid
return n
null
scanning_error_rate = |fields, tickets|
tickets
.each |ticket|
match find_invalid_field ticket, fields
null then 0
n then n
.sum()
assign_field_ids = |fields, tickets|
# identify which tickets are valid
valid_tickets = tickets
.keep |ticket| not find_invalid_field ticket, fields
.to_tuple()
# find out which fields could be a match for each column
column_count = fields.size()
for i in 0..column_count
for _, field in fields
could_be_a_match = valid_tickets.all |ticket|
n = ticket[i]
field.rules.any |rule| rule.contains n
if could_be_a_match
field.update "possible_ids", [], |possible_ids|
possible_ids.push i
# Assign field ids by sorting them in order of number of possibilities,
# the first field will have a single option which can be assigned,
# and then that possibility can be eliminated from the other fields.
pending_fields = copy fields
pending_fields.sort |_, field| field.possible_ids.size()
until pending_fields.is_empty()
name, field = pending_fields.get_index(0)
match field.possible_ids
[index] then
fields.get(name).id = index
pending_fields.remove name
for _, field in pending_fields
n_index = field.possible_ids.position |n| n == index
field.possible_ids.remove n_index
else assert false
analyze_ticket = |ticket, fields|
result = fields
.keep |(name, _)| name.starts_with "departure"
.each |(_, field)| ticket[field.id]
.product()
@main = ||
fields, your_ticket, nearby_tickets =
extend_path script_dir, "input", "16"
>> read_to_string
>> parse_notes
print "Part one: ${scanning_error_rate fields, nearby_tickets}" # 26053
assign_field_ids fields, nearby_tickets
print "Part two: ${analyze_ticket your_ticket, fields}" # 1515506256421
@tests =
@test part_one: ||
fields, your_ticket, nearby_tickets =
extend_path script_dir, "input", "16-example-1"
>> read_to_string
>> parse_notes
assert_eq (scanning_error_rate fields, nearby_tickets), 71
@test part_two: ||
fields, your_ticket, nearby_tickets =
extend_path script_dir, "input", "16-example-2"
>> read_to_string
>> parse_notes
assign_field_ids fields, nearby_tickets
assert_eq (fields.row.id), 0
assert_eq (fields.class.id), 1
assert_eq (fields.seat.id), 2