-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathday16.php
More file actions
236 lines (195 loc) · 9.1 KB
/
day16.php
File metadata and controls
236 lines (195 loc) · 9.1 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
<?php
/**
--- Day 16: Ticket Translation ---
As you're walking to yet another connecting flight, you realize that one of the legs of your re-routed trip coming up is on a high-speed train. However, the train ticket you were given is in a language you don't understand. You should probably figure out what it says before you get to the train station after the next flight.
Unfortunately, you can't actually read the words on the ticket. You can, however, read the numbers, and so you figure out the fields these tickets must have and the valid ranges for values in those fields.
You collect the rules for ticket fields, the numbers on your ticket, and the numbers on other nearby tickets for the same train service (via the airport security cameras) together into a single document you can reference (your puzzle input).
The rules for ticket fields specify a list of fields that exist somewhere on the ticket and the valid ranges of values for each field. For example, a rule like class: 1-3 or 5-7 means that one of the fields in every ticket is named class and can be any value in the ranges 1-3 or 5-7 (inclusive, such that 3 and 5 are both valid in this field, but 4 is not).
Each ticket is represented by a single line of comma-separated values. The values are the numbers on the ticket in the order they appear; every ticket has the same format. For example, consider this ticket:
.--------------------------------------------------------.
| ????: 101 ?????: 102 ??????????: 103 ???: 104 |
| |
| ??: 301 ??: 302 ???????: 303 ??????? |
| ??: 401 ??: 402 ???? ????: 403 ????????? |
'--------------------------------------------------------'
Here, ? represents text in a language you don't understand. This ticket might be represented as 101,102,103,104,301,302,303,401,402,403; of course, the actual train tickets you're looking at are much more complicated. In any case, you've extracted just the numbers in such a way that the first number is always the same specific field, the second number is always a different specific field, and so on - you just don't know what each position actually means!
Start by determining which tickets are completely invalid; these are tickets that contain values which aren't valid for any field. Ignore your ticket for now.
For example, suppose you have the following notes:
class: 1-3 or 5-7
row: 6-11 or 33-44
seat: 13-40 or 45-50
your ticket:
7,1,14
nearby tickets:
7,3,47
40,4,50
55,2,20
38,6,12
It doesn't matter which position corresponds to which field; you can identify invalid nearby tickets by considering only whether tickets contain values that are not valid for any field. In this example, the values on the first nearby ticket are all valid for at least one field. This is not true of the other three nearby tickets: the values 4, 55, and 12 are are not valid for any field. Adding together all of the invalid values produces your ticket scanning error rate: 4 + 55 + 12 = 71.
Consider the validity of the nearby tickets you scanned. What is your ticket scanning error rate?
Your puzzle answer was 19070.
--- Part Two ---
Now that you've identified which tickets contain invalid values, discard those tickets entirely. Use the remaining valid tickets to determine which field is which.
Using the valid ranges for each field, determine what order the fields appear on the tickets. The order is consistent between all tickets: if seat is the third field, it is the third field on every ticket, including your ticket.
For example, suppose you have the following notes:
class: 0-1 or 4-19
row: 0-5 or 8-19
seat: 0-13 or 16-19
your ticket:
11,12,13
nearby tickets:
3,9,18
15,1,5
5,14,9
Based on the nearby tickets in the above example, the first position must be row, the second position must be class, and the third position must be seat; you can conclude that in your ticket, class is 12, row is 11, and seat is 13.
Once you work out which field is which, look for the six fields on your ticket that start with the word departure. What do you get if you multiply those six values together?
Your puzzle answer was 161926544831.
*/
$data = file_get_contents('Data/day16.txt');
[$rulesString, $myTicket, $nearbyTicketsString] = explode("\n\r", $data);
$startTime = round(microtime(true) * 1000);
echo "Part 1 answer is " . day16part1($rulesString, $nearbyTicketsString) . PHP_EOL;
echo 'Elapsed time ' . round(microtime(true) * 1000) - $startTime . "ms" . PHP_EOL;
$startTime = round(microtime(true) * 1000);
echo "Part 2 answer is " . day16part2($myTicket, $nearbyTicketsString, $rulesString) . PHP_EOL;
echo 'Elapsed time ' . round(microtime(true) * 1000) - $startTime . "ms" . PHP_EOL;
function day16part1($rulesString, $nearbyTicketsString)
{
return calculateErrorRating($rulesString, $nearbyTicketsString);
}
function day16part2($myTicket, $nearbyTicketsString, $rulesString)
{
$myTicket = parseNearbyTickets($myTicket);
$nearbyTickets = parseNearbyTickets($nearbyTicketsString);
$rules = parseRules($rulesString);
[$rulesIndividual, $validTickets] = calculateIndividualRules($rulesString, $nearbyTickets, $rules);
$validatedRules = validateTickets($myTicket[0], $validTickets, $rulesIndividual);
$ruleOrder = determineOrder($validatedRules);
return calculateRating($ruleOrder, $myTicket[0]);
}
function parseRules(string $rules)
{
$finalRules = [];
preg_match_all("/.*?: (\d+-\d+) or (\d+-\d+)/", $rules, $rulesMatches);
unset($rulesMatches[0]);
foreach ($rulesMatches as $set) {
foreach ($set as $index => $rule) {
$ends = explode('-', $rule);
for ($i = $ends[0]; $i <= $ends[1]; $i++) {
$finalRules[$i] = true;
}
}
}
return $finalRules;
}
function parseRulesIndividual(string $rules, $extraPattern = '')
{
$finalRules = [];
preg_match_all("/$extraPattern.*?: (\d+-\d+) or (\d+-\d+)/", $rules, $rulesMatches);
unset($rulesMatches[0]);
foreach ($rulesMatches as $set) {
foreach ($set as $index => $rule) {
$ends = explode('-', $rule);
for ($i = $ends[0]; $i <= $ends[1]; $i++) {
$finalRules[$index][$i] = true;
}
}
}
return $finalRules;
}
function parseNearbyTickets(string $nearbyTickets)
{
$nearbyTickets = explode(":\r\n", $nearbyTickets)[1];
$nearbyTickets = explode("\n", $nearbyTickets);
foreach ($nearbyTickets as $index => $line) {
$nearbyTickets[$index] = explode(',', $line);
}
return $nearbyTickets;
}
function calculateErrorRating(string $rulesString, string $nearbyTicketsString)
{
$nearbyTickets = parseNearbyTickets($nearbyTicketsString);
$rules = parseRules($rulesString);
$badTicketsSum = 0;
foreach ($nearbyTickets as $ticket) {
foreach ($ticket as $number) {
if (!isset($rules[trim($number)])) {
$badTicketsSum += $number;
}
}
}
return $badTicketsSum;
}
function calculateIndividualRules(string $rulesString, array $nearbyTickets, array $rules): array
{
$rulesIndividual = parseRulesIndividual($rulesString);
$validTickets = [];
foreach ($nearbyTickets as $ticket) {
$badTicket = false;
foreach ($ticket as $number) {
if (!isset($rules[trim($number)])) {
$badTicket = true;
continue;
}
}
if (!$badTicket) {
$validTickets[] = $ticket;
}
}
return [$rulesIndividual, $validTickets];
}
/**
* @param array $myTicket
* @param array $validTickets
* @param array $rulesIndividual
* @return array
*/
function validateTickets(array $myTicket, array $validTickets, array $rulesIndividual): array
{
$validTickets[] = $myTicket;
$countFields = count($myTicket);
$validatedRules = [];
for ($i = 0; $i < $countFields; $i++) {
foreach ($rulesIndividual as $ruleIndex => $rule) {
$valid = true;
foreach ($validTickets as $ticket) {
if (!isset($rule[trim($ticket[$i])])) {
$valid = false;
}
}
if ($valid) {
$validatedRules[$i][] = $ruleIndex;
continue;
}
}
}
return $validatedRules;
}
function determineOrder(array $validatedRules): array
{
$ruleOrder = [];
while (count($validatedRules) != 0) {
foreach ($validatedRules as $field => $rules) {
if (count($rules) == 1) {
$determinedRule = max($rules);
$ruleOrder[$field] = $determinedRule;
unset($validatedRules[$field]);
foreach ($validatedRules as $fieldIndex => $iteratedRule) {
$key = array_search($determinedRule, $iteratedRule);
unset($validatedRules[$fieldIndex][$key]);
}
}
}
}
$ruleOrder = array_flip($ruleOrder);
ksort($ruleOrder);
return $ruleOrder;
}
function calculateRating(array $ruleOrder, array $myTicket): int
{
$result = 1;
for ($i = 0; $i < 6; $i++) {
$result *= $myTicket[$ruleOrder[$i]];
}
return $result;
}