Skip to content

Commit eadb108

Browse files
authored
2015 Day 14 part 1 & 2 (#24)
1 parent 11f865f commit eadb108

File tree

6 files changed

+292
-1
lines changed

6 files changed

+292
-1
lines changed

2015/day-14.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
ini_set("memory_limit", "-1");
4+
5+
require __DIR__ . '/../vendor/autoload.php';
6+
7+
use adventofcode\Year2015\ReindeerOlympics;
8+
9+
/**
10+
* --- Day 14: Reindeer Olympics ---
11+
*
12+
* This year is the Reindeer Olympics! Reindeer can fly at high speeds, but must rest occasionally to recover their
13+
* energy. Santa would like to know which of his reindeer is fastest, and so he has them race.
14+
*
15+
* Reindeer can only either be flying (always at their top speed) or resting (not moving at all), and always spend
16+
* whole seconds in either state.
17+
*
18+
* For example, suppose you have the following Reindeer:
19+
*
20+
* - Comet can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.
21+
* - Dancer can fly 16 km/s for 11 seconds, but then must rest for 162 seconds.
22+
*
23+
* After one second, Comet has gone 14 km, while Dancer has gone 16 km. After ten seconds, Comet has gone 140 km,
24+
* while Dancer has gone 160 km. On the eleventh second, Comet begins resting (staying at 140 km), and Dancer
25+
* continues on for a total distance of 176 km. On the 12th second, both reindeer are resting. They continue to
26+
* rest until the 138th second, when Comet flies for another ten seconds. On the 174th second, Dancer flies
27+
* for another 11 seconds.
28+
*
29+
* In this example, after the 1000th second, both reindeer are resting, and Comet is in the lead at 1120 km
30+
* (poor Dancer has only gotten 1056 km by that point). So, in this situation, Comet would win
31+
* (if the race ended at 1000 seconds).
32+
*
33+
* Given the descriptions of each reindeer (in your puzzle input), after exactly 2503 seconds, what distance
34+
* has the winning reindeer traveled?
35+
*/
36+
37+
$reindeerOlympics = new ReindeerOlympics();
38+
$input = file(__DIR__ . '/inputs/day-14.input', FILE_IGNORE_NEW_LINES);
39+
$result = $reindeerOlympics->determineFlightWinnerDistance($input, 2503);
40+
print('The winning reindeer flew ' . $result . " kilometers.\n");
41+
42+
/**
43+
* --- Part Two ---
44+
*
45+
* Seeing how reindeer move in bursts, Santa decides he's not pleased with the old scoring system.
46+
*
47+
* Instead, at the end of each second, he awards one point to the reindeer currently in the lead. (If there are
48+
* multiple reindeer tied for the lead, they each get one point.) He keeps the traditional 2503 second time limit,
49+
* of course, as doing otherwise would be entirely ridiculous.
50+
*
51+
* Given the example reindeer from above, after the first second, Dancer is in the lead and gets one point. He stays
52+
* in the lead until several seconds into Comet's second burst: after the 140th second, Comet pulls into the lead
53+
* and gets his first point. Of course, since Dancer had been in the lead for the 139 seconds before that, he has
54+
* accumulated 139 points by the 140th second.
55+
*
56+
* After the 1000th second, Dancer has accumulated 689 points, while poor Comet, our old champion, only has 312.
57+
* So, with the new scoring system, Dancer would win (if the race ended at 1000 seconds).
58+
*
59+
* Again given the descriptions of each reindeer (in your puzzle input), after exactly 2503 seconds, how many
60+
* points does the winning reindeer have?
61+
*/
62+
63+
$result = $reindeerOlympics->determineFlightWinnerPoints($input, 2503);
64+
print('The winning reindeer accumulated ' . $result . " points.\n");

2015/inputs/day-14-sample.input

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Comet can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.
2+
Dancer can fly 16 km/s for 11 seconds, but then must rest for 162 seconds.

2015/inputs/day-14.input

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Vixen can fly 19 km/s for 7 seconds, but then must rest for 124 seconds.
2+
Rudolph can fly 3 km/s for 15 seconds, but then must rest for 28 seconds.
3+
Donner can fly 19 km/s for 9 seconds, but then must rest for 164 seconds.
4+
Blitzen can fly 19 km/s for 9 seconds, but then must rest for 158 seconds.
5+
Comet can fly 13 km/s for 7 seconds, but then must rest for 82 seconds.
6+
Cupid can fly 25 km/s for 6 seconds, but then must rest for 145 seconds.
7+
Dasher can fly 14 km/s for 3 seconds, but then must rest for 38 seconds.
8+
Dancer can fly 3 km/s for 16 seconds, but then must rest for 37 seconds.
9+
Prancer can fly 25 km/s for 6 seconds, but then must rest for 143 seconds.

2015/src/ReindeerOlympics.php

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
<?php
2+
3+
namespace adventofcode\Year2015;
4+
5+
class ReindeerOlympics
6+
{
7+
/**
8+
* Determine the maximum distance flown from a given set of reindeer.
9+
*
10+
* @param array $reindeerData
11+
* @param int $interval
12+
* @return float
13+
*/
14+
public function determineFlightWinnerDistance(array $reindeerData, int $interval): float
15+
{
16+
$winnersDistance = 0;
17+
18+
foreach ($reindeerData as $reindeer) {
19+
$data = $this->parseReindeerData($reindeer);
20+
$distance = $this->calculateDistance(
21+
$data['speed'],
22+
$data['flightTime'],
23+
$data['oneSecondFlightDistance'],
24+
$data['restDuration'],
25+
$interval
26+
);
27+
if ($distance > $winnersDistance) {
28+
$winnersDistance = $distance;
29+
}
30+
}
31+
32+
return $winnersDistance;
33+
}
34+
35+
/**
36+
* Determine the winning points from a given set of reindeer.
37+
*
38+
* @param array $reindeerData
39+
* @param int $interval
40+
* @return int
41+
*/
42+
public function determineFlightWinnerPoints(array $reindeerData, int $interval): int
43+
{
44+
$contestants = [];
45+
46+
foreach ($reindeerData as $reindeer) {
47+
$data = $this->parseReindeerData($reindeer);
48+
$contestants[] = $data;
49+
}
50+
51+
for ($i = 0; $i < $interval; $i++) {
52+
// Determine each reindeer's current distance.
53+
foreach ($contestants as $index => $contestant) {
54+
if (!$contestant['isResting']) {
55+
$contestants[$index]['currentDistance'] += $contestant['speed'];
56+
57+
if ($contestants[$index]['currentLeg'] + 1 === $contestant['flightTime']) {
58+
$contestants[$index]['isResting'] = true;
59+
$contestants[$index]['currentLeg'] = 0;
60+
61+
continue;
62+
}
63+
64+
$contestants[$index]['currentLeg']++;
65+
}
66+
67+
if ($contestant['isResting'] && $contestant['currentRestTime'] + 1 === $contestant['restDuration']) {
68+
$contestants[$index]['isResting'] = false;
69+
$contestants[$index]['currentRestTime'] = 0;
70+
71+
continue;
72+
}
73+
74+
if ($contestant['isResting']) {
75+
$contestants[$index]['currentRestTime']++;
76+
}
77+
}
78+
79+
// Find the largest current distance
80+
$largestDistance = 0;
81+
82+
foreach ($contestants as $contestant) {
83+
if ($contestant['currentDistance'] > $largestDistance) {
84+
$largestDistance = $contestant['currentDistance'];
85+
}
86+
}
87+
88+
// Update the scores for those in the lead.
89+
foreach ($contestants as $index => $contestant) {
90+
if ($contestant['currentDistance'] === $largestDistance) {
91+
$contestants[$index]['currentPoints']++;
92+
}
93+
}
94+
}
95+
96+
$winnersPoints = 0;
97+
98+
foreach ($contestants as $contestant) {
99+
if ($contestant['currentPoints'] > $winnersPoints) {
100+
$winnersPoints = $contestant['currentPoints'];
101+
}
102+
}
103+
104+
return $winnersPoints;
105+
}
106+
107+
/**
108+
* Find the distance covered over a given interval based on the given speed, flight time, and rest duration.
109+
* All values are in seconds.
110+
*
111+
* @param int $speed
112+
* @param int $flightTime
113+
* @param float $oneSecondFlightDistance
114+
* @param int $restDuration
115+
* @param int $interval
116+
* @return float
117+
*/
118+
protected function calculateDistance(
119+
int $speed,
120+
int $flightTime,
121+
float $oneSecondFlightDistance,
122+
int $restDuration,
123+
int $interval
124+
): float {
125+
$distance = 0;
126+
127+
$maxFlightTime = $speed * $flightTime;
128+
129+
$currentTime = 0;
130+
while ($currentTime < $interval) {
131+
if ($maxFlightTime > $interval) {
132+
$distance += $oneSecondFlightDistance * ($interval - $currentTime);
133+
$currentTime = $interval;
134+
continue;
135+
}
136+
137+
if ($maxFlightTime + $restDuration > $interval) {
138+
$distance += $maxFlightTime + ($interval - $currentTime);
139+
$currentTime = $interval;
140+
continue;
141+
}
142+
143+
$distance += $maxFlightTime;
144+
$currentTime += $flightTime + $restDuration;
145+
}
146+
147+
return $distance;
148+
}
149+
150+
/**
151+
* Parse a string of reindeer flight data.
152+
*
153+
* @param string $reindeer
154+
* @return array
155+
*/
156+
protected function parseReindeerData(string $reindeer): array
157+
{
158+
preg_match('/(.*) can fly (\d+) km\/s for (\d+).*for (\d+).*/', $reindeer, $matches);
159+
160+
return [
161+
'name' => $matches[1],
162+
'speed' => intval($matches[2]),
163+
'flightTime' => intval($matches[3]),
164+
'oneSecondFlightDistance' => $matches[2] / $matches[3],
165+
'maxFlightTime' => $matches[2] * $matches[3],
166+
'restDuration' => intval($matches[4]),
167+
'currentDistance' => 0,
168+
'currentPoints' => 0,
169+
'currentLeg' => 0,
170+
'isResting' => false,
171+
'currentRestTime' => 0,
172+
];
173+
}
174+
}

2015/tests/ReindeerOlympicsTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace adventofcode\Year2015\Test;
4+
5+
use adventofcode\Year2015\ReindeerOlympics;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class ReindeerOlympicsTest extends TestCase
9+
{
10+
protected ReindeerOlympics $reindeerOlympics;
11+
protected array $sampleInput;
12+
protected array $input;
13+
14+
protected function setup(): void
15+
{
16+
$this->reindeerOlympics = new ReindeerOlympics();
17+
$this->sampleInput = file(__DIR__ . '/../inputs/day-14-sample.input', FILE_IGNORE_NEW_LINES);
18+
$this->input = file(__DIR__ . '/../inputs/day-14.input', FILE_IGNORE_NEW_LINES);
19+
}
20+
21+
public function testDetermineFlightWinnerDistance()
22+
{
23+
// Day 14 sample input
24+
$result = $this->reindeerOlympics->determineFlightWinnerDistance($this->sampleInput, 1000);
25+
$this->assertEquals(1120, $result);
26+
27+
// Day 14 input
28+
$result = $this->reindeerOlympics->determineFlightWinnerDistance($this->input, 2503);
29+
$this->assertEquals(2660, $result);
30+
}
31+
32+
public function testDetermineFlightWinnerPoints()
33+
{
34+
// Day 14 sample input
35+
$result = $this->reindeerOlympics->determineFlightWinnerPoints($this->sampleInput, 1000);
36+
$this->assertEquals(689, $result);
37+
38+
// Day 14 input
39+
$result = $this->reindeerOlympics->determineFlightWinnerPoints($this->input, 2503);
40+
$this->assertEquals(1256, $result);
41+
}
42+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ of changes and trends in that community.
2424
| Day 11 | | | | | | | | | |
2525
| Day 12 | | | | | | | | | |
2626
| Day 13 | | | | | | | | | |
27-
| Day 14 | | | | | | | | | |
27+
| Day 14 | | | | | | | | | :star: :star: |
2828
| Day 15 | | | | | | | | | |
2929
| Day 16 | | | | | | | | | |
3030
| Day 17 | | | | | | | | | |

0 commit comments

Comments
 (0)