-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathday_14.ex
136 lines (121 loc) · 3.45 KB
/
day_14.ex
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
defmodule AdventOfCode.Y2015.Day14 do
@moduledoc """
--- Day 14: Reindeer Olympics ---
Problem Link: https://adventofcode.com/2015/day/14
Difficulty: s
Tags: measurement recursion sequence
"""
alias AdventOfCode.Helpers.{InputReader, Transformers}
def input, do: InputReader.read_from_file(2015, 14)
@type outcome :: {binary(), stats(), list(non_neg_integer())}
@type reindeer :: %{
name: binary(),
speed: non_neg_integer(),
duration: non_neg_integer(),
rest: non_neg_integer()
}
@type states :: :resting | :running
@type stats :: %{
moving: non_neg_integer(),
resting: non_neg_integer(),
distance: non_neg_integer()
}
@end_time 2503
def run(input \\ input()) do
input = parse(input)
{run_1(input), run_2(input)}
end
defp run_1(input) do
input
|> Enum.map(&race(&1, @end_time))
|> Enum.max_by(fn {_, %{distance: distance}, _} -> distance end)
|> then(fn {_, %{distance: distance}, _} -> distance end)
end
defp run_2(input) do
input
|> Enum.map(&elem(race(&1, @end_time), 2))
|> Transformers.transpose()
|> Enum.map(fn same_second_distance ->
ahead = Enum.max(same_second_distance)
Enum.map(same_second_distance, fn distance -> (distance == ahead && 1) || 0 end)
end)
|> Transformers.transpose()
|> Enum.map(&Enum.sum/1)
|> Enum.max()
end
def parse(data \\ input()) do
data
|> Transformers.lines()
|> Enum.map(&parse_stats/1)
end
@regex ~r{(\w+) can fly (\d+) km/s for (\d+) seconds, but then must rest for (\d+) seconds.}
def parse_stats(line) do
@regex
|> Regex.run(line, capture: :all_but_first)
|> then(fn [name, speed, duration, rest] ->
%{
name: name,
speed: String.to_integer(speed),
duration: String.to_integer(duration),
rest: String.to_integer(rest)
}
end)
end
@spec race(reindeer(), non_neg_integer()) :: outcome()
def race(reindeer, limit) do
race(
reindeer,
{limit, 0},
:running,
%{moving: 0, resting: 0, distance: 0},
[]
)
end
@spec race(
reindeer(),
{non_neg_integer(), non_neg_integer()},
states(),
stats(),
list(non_neg_integer())
) ::
outcome()
def race(%{name: name}, {limit, limit}, _, stats, distances), do: {name, stats, distances}
def race(
%{speed: speed, duration: duration} = reindeer,
{limit, elapsed},
:running,
%{moving: moving, distance: distance} = stats,
distances
)
when moving < duration do
race(
reindeer,
{limit, elapsed + 1},
:running,
%{stats | moving: moving + 1, distance: distance + speed},
[distance + speed | distances]
)
end
def race(reindeer, {limit, elapsed}, :running, stats, distances) do
race(reindeer, {limit, elapsed}, :resting, %{stats | moving: 0}, distances)
end
def race(
%{rest: rest} = reindeer,
{limit, elapsed},
:resting,
%{resting: resting} = stats,
[distance | _] = distances
)
when resting < rest do
race(
reindeer,
{limit, elapsed + 1},
:resting,
%{stats | resting: resting + 1},
[distance | distances]
)
end
def race(reindeer, {limit, elapsed}, :resting, stats, distances) do
race(reindeer, {limit, elapsed}, :running, %{stats | resting: 0}, distances)
end
end