Advent of Code 2022
Introduction
These are solutions for Advent of Code 2022 written in Elixir. Elixir Livebook is utilized to create this notebook.
The overall strategy is domain-driven design, in that each puzzle is modeled as a domain using Elixir types and functions. The intent is not to provide “clever” solutions that often utilize some sort of computational trickery but to provide a clear linear path from problem statement, a domain model of the problem, an implementation of that model, and then ultimately a solution. It is considered a failure if the solution does not essentially read like an Elixir encoding of the problem description.
The following are provided:
- Full solutions for each day
- Documented modules, types, and functions
- Typespecs for every type and function
- Tests to help verify refactors that occur after a correct solution is arrived at
It is for these reasons that the solutions are somewhat “verbose”, if one really wants to use that term. I don’t necessarily because I view these solutions are the minimum form required to provided the necessary function, a perspective of form equals function. The solutions provided here are fully industrialized and tend to be quite amenable to refactors, often making the transition from part one to part two quite nice and straightforward.
For the most part, solutions are contained within a single Day
module. Solution values for the two parts are provided as functions &part_one/0
and &part_two/0
. However, some days may drastically alter the solution from part one. In cases such as those, two separate modules for the day, such as Day.PartOne
and Day.PartTwo
. Solution values in this case are provided as &solution/0
in the respective module.
Contents
Utilities
defmodule Utilities do
@moduledoc """
Provides utility functions to be used across days
"""
@doc """
Reads the given day's data file of "day__input.txt" as
a stream
"""
@spec read_data(integer(), keyword()) :: Stream.t()
def read_data(day, opts \\ [trim: true]) do
day_as_string =
day
|> Integer.to_string()
|> String.pad_leading(2, "0")
lines =
Path.join(__DIR__, "../data/day_#{day_as_string}_input.txt")
|> Path.expand()
|> File.stream!()
if opts[:trim] do
Stream.map(lines, &String.trim/1)
else
lines
end
end
@doc """
Tests whether the entire string is uppercase
"""
@spec uppercase?(String.t()) :: boolean()
def uppercase?(string), do: string == String.upcase(string)
@doc """
Tests whether the entire string is lowercase
"""
@spec lowercase?(String.t()) :: boolean()
def lowercase?(string), do: string == String.downcase(string)
@doc """
Transposes the given rows. If the rows are not all of the same length, then
the lengths are normalized to the shortest row.
## Examples:
iex> transpose([1, 2, 3], [4, 5, 6])
[[1, 2], [3, 4], [5, 6]]
"""
@spec transpose([any()]) :: [any()]
def transpose(rows) do
rows
|> List.zip()
|> Enum.map(&Tuple.to_list/1)
end
@doc """
Tests if the string is the empty string
## Examples:
iex> empty?("test")
false
iex> empty?("")
true
"""
@spec empty?(String.t()) :: boolean()
def empty?(string) do
string == ""
end
@doc """
Tests if the string is not the empty string
## Examples:
iex> non_empty?("test")
true
iex> non_empty?("")
false
"""
@spec non_empty?(String.t()) :: boolean()
def non_empty?(string) do
string != ""
end
@doc """
Drops the last element of the enum
"""
@spec drop_last(Enum.t()) :: Enum.t()
def drop_last(enum) do
enum
|> Enum.reverse()
|> tl()
|> Enum.reverse()
end
@doc """
Tests if all the elements of the enumerable are unique
"""
@spec unique_elements?(Enum.t()) :: boolean()
def unique_elements?(enum) do
length(enum) == length(Enum.uniq(enum))
end
end
{:module, Utilities, <<70, 79, 82, 49, 0, 0, 20, ...>>, {:unique_elements?, 1}}
defmodule Stack do
@moduledoc """
A stack data structure
"""
@enforce_keys [:elements]
defstruct elements: []
@typedoc """
Represents a stack where the given `element_type` parameter is the type of the
stack's elements
"""
@type t(element_type) :: %__MODULE__{
elements: list(element_type)
}
@doc """
Create a new stack from the given elements. The order of the list is preserved
such that the head of the given element list becomes the top of the stack.
"""
@spec new([stack_type]) :: __MODULE__.t(stack_type) when stack_type: any()
def new(initial_elements) do
%__MODULE__{
elements: initial_elements
}
end
@doc """
Push the element onto the top of the stack and return the stack
"""
@spec push(__MODULE__.t(stack_type), stack_type) :: __MODULE__.t(stack_type)
when stack_type: any()
def push(%__MODULE__{elements: elements}, element) do
new([element | elements])
end
@doc """
Push the elements onto the top of the stack and return the stack. The elements
are not pushed one at a time (thus reversing their order from the order as given)
but are instead pushed all at once as a group (the opposite order from if they were pushed
one at a time).
"""
@spec push_as_group(__MODULE__.t(stack_type), [stack_type]) :: __MODULE__.t(stack_type)
when stack_type: any()
def push_as_group(%__MODULE__{elements: elements}, new_elements) do
new(new_elements ++ elements)
end
@doc """
Pops the element at the top of the stack off of the stack and returns a tuple
consisting of the element and the remaining stack.
"""
@spec pop(__MODULE__.t(stack_type)) :: {stack_type, __MODULE__.t(stack_type)}
when stack_type: any()
def pop(%__MODULE__{elements: [head | tail]}) do
{head, new(tail)}
end
@doc """
Peeks the element at the top of the stack and returns it
"""
@spec peek(__MODULE__.t(stack_type)) :: stack_type when stack_type: any()
def peek(%__MODULE__{elements: [head | _tail]}) do
head
end
@doc """
Peeks the number of elements at the top of the stack and returns them
"""
@spec peek(__MODULE__.t(stack_type), integer()) :: stack_type when stack_type: any()
def peek(%__MODULE__{elements: elements}, n) do
Enum.take(elements, n)
end
@doc """
Drops the element at the top of the stack and returns the remaining stack
"""
@spec drop(__MODULE__.t(stack_type)) :: __MODULE__.t(stack_type) when stack_type: any()
def drop(%__MODULE__{elements: [_head | tail]}) do
new(tail)
end
@doc """
Drops the number of elements on top of the stack and returns the remaining stack
"""
@spec drop(__MODULE__.t(stack_type), integer()) :: __MODULE__.t(stack_type)
when stack_type: any()
def drop(%__MODULE__{elements: elements}, n) do
elements_left = Enum.drop(elements, n)
new(elements_left)
end
end
{:module, Stack, <<70, 79, 82, 49, 0, 0, 21, ...>>, {:drop, 2}}
Day 1
https://adventofcode.com/2022/day/1
defmodule Day1 do
@moduledoc """
Solutions for Day 1
"""
@doc """
Map of every elf's list of calories keyed by the index in order of appearance
in the given data input file
"""
@spec calories() :: %{(elf_index :: pos_integer()) => elf_calories :: [pos_integer()]}
def calories() do
handleChunk = fn chunk ->
chunk
|> Enum.reverse()
|> Enum.map(&String.to_integer/1)
end
chunk_fun = fn element, acc ->
if element == "" do
# Emit chunk and reset accumulator
{:cont, handleChunk.(acc), []}
else
{:cont, [element | acc]}
end
end
after_fun = fn acc ->
{:cont, handleChunk.(acc), []}
end
Utilities.read_data(1)
|> Stream.chunk_while([], chunk_fun, after_fun)
|> Stream.with_index()
|> Enum.into(%{}, fn {value, key} -> {key, value} end)
end
@doc """
Returns the maximum total calories for a single elf
"""
@spec max_calories() :: pos_integer()
def max_calories() do
calories()
|> Enum.max(fn {_, a}, {_, b} -> Enum.sum(a) >= Enum.sum(b) end)
end
@doc """
Returns a sorted list of tuples containing the elf's index and list of calories.
The list is sorted by the total sum of each elf's calories.
"""
@spec sorted_calories() :: [{elf_index :: pos_integer(), elf_calories :: [pos_integer()]}]
def sorted_calories() do
calories()
|> Enum.sort(fn {_, a}, {_, b} -> Enum.sum(a) >= Enum.sum(b) end)
end
def part_one() do
{_elf, calories} = max_calories()
calories |> Enum.sum()
end
def part_two() do
sorted_calories()
|> Enum.take(3)
|> Enum.map(fn {_k, v} -> Enum.sum(v) end)
|> Enum.sum()
end
end
{:module, Day1, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:part_two, 0}}
Day1.calories()
%{
33 => [1365, 4100, 3131, 3596, 4719, 4250, 4580, 5418, 1687, 6533, 5938, 5865, 4605],
168 => [17215, 9922, 6402, 19625],
117 => [14249, 5887, 12930, 2407, 14495],
246 => [3583, 1792, 5592, 5648, 5637, 3586, 1685, 1042, 4020, 6204, 4111, 4887, 6858],
175 => [4649, 7673, 4275, 3178, 5322, 4959, 5990, 1480, 5206, 3693],
219 => [3959, 1907, 5456, 2086, 3711, 5971, 3294, 1861, 2780, 1913, 4832, 5076, 1830],
12 => [8585, 9072, 3466],
192 => [5832],
188 => [5674, 1979, 7064, 2839, 6346, 2751, 1055, 3565, 6608, 2640, 2418],
157 => [4261, 2686, 1891, 5516, 5035, 1525, 5360, 4027, 3811, 6325, 5813, 4172, 6415],
132 => [4848, 2725, 1146, 5588, 1671, 4461, 1109, 2933, 1639, 4587, 6680, 1035, 3288],
73 => [4927, 5047, 12168, 15288, 14202],
44 => [1273, 2231, 3534, 4970, 1685, 2565, 1847, 5926, 5778, 4919, 4238, 1422, 3945, 1252, 4234],
183 => [4998, 5114, 4812, 7291, 1189, 1140, 7418, 6180, 3614, 6185, 7174, 4112],
124 => [7386, 9544, 7237],
239 => [1071, 25798, 5915],
170 => [14115, 1542, 17633],
23 => [5148, 3451, 2735, 12950, 6576, 10785],
29 => [2150, 2901, 3490, 4291, 8998, 5673, 3623, 3894, 7435],
47 => [1501, 6913, 4990, 5948, 2597, 5644, 2876, 3515, 4264, 3768, 4731, 3944],
89 => [8427, 7538, 7405, 6634, 2889, 1705, 7171, 6067, 3737],
203 => [9432, 11708, 8636, 10825, 5787, 12812],
61 => [2340, 9011, 8717, 1458, 11325, 4563, 8634],
30 => [2235, 3164, 1231, 3675, 4188, 4865, 1611, 4652, 6333, 3262, 3124, 6215, 2704, 3183],
43 => [3162, 4035, 1359, 4364, 2392, 3819, 5132, 4721, 4794, 1622, 4693, 5318, 6089, 1588, 3199],
163 => [25171, 8046, 4857],
39 => [2814, 5388, 1641, 1526, 4489, 2291, 2573, 5389, 3109, 3422, 5342, 1461, 3105, 4265],
131 => [10460, 3439, 10344, 4353, 14564],
45 => [9120, 13317, 3969, 4758, 13615, 7951],
242 => [2689, 4455, 3461, 5468, 1308, 1111, 4181, 3832, 1172, 2809, 3782, 2943, 1926, 1470, 5324],
235 => [3088, 5831, 5452, 2038, 1116, 3670, 3025, 3749, 5040, 2507, 3787, 1674, 4437, 2630, 5239],
48 => [10132, 19382],
145 => [1799, 3098, 4398, 3948, 4257, 4112, 1364, 3813, 5036, 3469, 4468, 2531, 1348],
247 => [9068],
171 => [3405, 3819, 1079, 2957, 2594, 3639, 1941, 3709],
197 => [1192, 3957, 4091, 2855, 2872, 3079, 6075, 2858, 5498, 1143, 3565, 5869, 5297, 1698],
57 => [7534, 2592, 1012, 4760, 2118, 8180, 1054, 3230, 7718],
143 => [7627, 3776, 7000, 10332, 4325, 3876, 2789, 8333],
237 => [2821, 7212, 7353, 5102, 3808, 7328, 2741, 1436, 6321, 6249, 6961],
221 => [2776, 1481, 3775, 5827, 7069, 6834, 2274, 5570, 4355, 5544, ...],
113 => [10645, 2358, 2163, 4477, 9860, 1345, 3691, 2085],
225 => [11157, 5983, 7801, 8726, 1466, 4673, 4161],
26 => [20192, 6831, 10425],
69 => [9323, 7633, 11404, 5092, 1683, 2705, ...],
88 => [2817, 26802],
250 => [5676, 7085, 3867, 9663, ...],
191 => [7035, 5732, 7114, ...],
166 => [6177, 7217, ...],
144 => [7701, ...],
209 => [...],
...
}
Day1.sorted_calories()
[
{34, [9739, 11547, 11940, 10268, 11939, 10825, 5522]},
{224, [24591, 21630, 25260]},
{75, [69228]},
{121, [33797, 34907]},
{148, [13509, 11975, 12287, 10636, 12399, 7668]},
{158, [24072, 19037, 25145]},
{46, [66512]},
{81, [66023]},
{6, [22590, 17677, 25444]},
{151, [5490, 4786, 4274, 5379, 4549, 4178, 2801, 1439, 5323, 4588, 5163, 1764, 4159, 5876, 5784]},
{36, [8045, 8298, 5813, 7881, 8605, 4114, 4524, 3837, 7631, 6763]},
{156, [11372, 15360, 14226, 7483, 16378]},
{2, [7069, 5792, 1519, 7380, 7034, 6203, 5706, 1850, 4933, 5562, 3826, 6661]},
{24, [5578, 5978, 5716, 4026, 1429, 7684, 6552, 7630, 5834, 4936, 7936]},
{0, [7769, 6798, 11685, 10826, 11807, 5786, 7932]},
{78, [5366, 3942, 4203, 4337, 4559, 6474, 4586, 5663, 3658, 5700, 6654, 6103, 1057]},
{83, [18357, 10466, 13614, 19749]},
{20, [7772, 10595, 6827, 4469, 10385, 3420, 7657, 8950]},
{31, [5215, 3259, 3550, 4965, 4096, 2036, 4517, 2554, 3266, 5571, 5266, 1466, 5259, 5882, 2782]},
{183, [4998, 5114, 4812, 7291, 1189, 1140, 7418, 6180, 3614, 6185, 7174, 4112]},
{203, [9432, 11708, 8636, 10825, 5787, 12812]},
{180, [4784, 1321, 5487, 6182, 5706, 5460, 5418, 2886, 5906, 6230, 1211, 1325, 3766, 3473]},
{58, [6017, 3402, 3896, 5801, 4055, 5034, 1063, 3694, 6663, 6079, 4364, 5712, 3366]},
{65, [2592, 4604, 2710, 2273, 4703, 1705, 5528, 5053, 5556, 1562, 5820, 5347, 2978, 2734, 5782]},
{186, [5484, 3947, 5030, 6716, 4840, 1353, 3667, 6815, 4884, 5050, 5678, 2224, 3010]},
{90, [5708, 1287, 1923, 6351, 5986, 3291, 1295, 6439, 3591, 2498, 5135, 2927, 5476, 6504]},
{201, [8320, 16041, 11934, 10727, 11090]},
{133, [10223, 9558, 8984, 6259, 8858, 4175, 4696, 4865]},
{209, [5634, 4583, 6213, 4733, 4093, 4552, 1115, 1413, 3362, 1079, 5891, 5299, 3532, 6085]},
{15, [3035, 9421, 4497, 1802, 8447, 5675, 7580, 9053, 8004]},
{237, [2821, 7212, 7353, 5102, 3808, 7328, 2741, 1436, 6321, 6249, 6961]},
{98, [12541, 15894, 16431, 12428]},
{17, [2982, 4005, 4036, 6510, 4817, 3958, 5057, 2049, 2603, 1227, 5960, 4043, 5261, 4780]},
{71, [9990, 12357, 17458, 17411]},
{162, [2745, 7978, 6573, 5295, 8034, 3894, 1410, 2764, 4644, 7553, 6257]},
{172, [5027, 7097, 2180, 5866, 6339, 10088, 10331, 10009]},
{221, [2776, 1481, 3775, 5827, 7069, 6834, 2274, 5570, 4355, 5544, 3954, ...]},
{157, [4261, 2686, 1891, 5516, 5035, 1525, 5360, 4027, 3811, 6325, ...]},
{194, [6075, 4309, 6002, 6426, 3744, 3310, 4451, 3076, 1509, ...]},
{177, [8495, 4414, 7618, 7494, 4141, 10026, 7653, 6703]},
{43, [3162, 4035, 1359, 4364, 2392, 3819, 5132, ...]},
{38, [5882, 2097, 3154, 4166, 1556, 3414, ...]},
{33, [1365, 4100, 3131, 3596, 4719, ...]},
{125, [6602, 6957, 3141, 3585, ...]},
{27, [3685, 8863, 1300, ...]},
{59, [4197, 7022, ...]},
{53, [7376, ...]},
{147, [...]},
{230, ...},
{...},
...
]
Day1.part_one()
71780
Day1.part_two()
212489
Day 2
https://adventofcode.com/2022/day/2
defmodule Day2.PartOne do
@moduledoc """
Solution for Day 2 Part One
"""
@typedoc """
Represents a move in the game of rock, paper, scissors
"""
@type move() :: :rock | :paper | :scissors
@typedoc """
Represents a single round of the game rock, paper, scissors
"""
@type round() :: %{
opponent: move(),
response: move()
}
@typedoc """
Represents the result of a single round of rock, paper, scissors
"""
@type result() :: :win | :lose | :draw
@doc """
Parses a move consisting of "A", "B", "C", "X", "Y", "Z" into the corresponding
move of `:rock`, `:paper`, or `:scissors`
"""
@spec parse_move(String.t()) :: move()
def parse_move(move) do
case move do
"A" -> :rock
"B" -> :paper
"C" -> :scissors
"X" -> :rock
"Y" -> :paper
"Z" -> :scissors
end
end
@doc """
List of all rounds
"""
@spec rounds() :: [round()]
def rounds() do
Utilities.read_data(2)
|> Stream.map(fn <> <> " " <> response ->
%{opponent: parse_move(opponent), response: parse_move(response)}
end)
|> Enum.to_list()
end
@doc """
Judge the given round to determine if it is a win, loss, or draw for the player
"""
@spec judge_round(round()) :: result()
def judge_round(%{opponent: opponent, response: response}) do
case {opponent, response} do
{:rock, :rock} -> :draw
{:rock, :paper} -> :win
{:rock, :scissors} -> :lose
{:paper, :rock} -> :lose
{:paper, :paper} -> :draw
{:paper, :scissors} -> :win
{:scissors, :rock} -> :win
{:scissors, :paper} -> :lose
{:scissors, :scissors} -> :draw
end
end
@doc """
Score the round according to the given rubric that calculates a score based upon the
reponse alone plus a score from the round's result
"""
@spec score_round(round()) :: pos_integer()
def score_round(%{opponent: _, response: response} = round) do
response_score =
case response do
:rock -> 1
:paper -> 2
:scissors -> 3
end
outcome_score =
case judge_round(round) do
:win -> 6
:lose -> 0
:draw -> 3
end
response_score + outcome_score
end
@doc """
A list of all the rounds' scores
"""
@spec scored_rounds() :: [pos_integer()]
def scored_rounds() do
rounds()
|> Enum.map(&score_round/1)
end
def solution(), do: scored_rounds() |> Enum.sum()
end
{:module, Day2.PartOne, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:solution, 0}}
Day2.PartOne.rounds()
[
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :scissors},
%{opponent: :scissors, response: :scissors},
%{opponent: :rock, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :scissors, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :scissors},
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :scissors, response: :paper},
%{opponent: :paper, response: :rock},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :scissors, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :scissors, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :rock},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :rock},
%{opponent: :paper, response: :paper},
%{opponent: :scissors, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :scissors, response: :scissors},
%{opponent: :rock, response: :rock},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :scissors},
%{opponent: :paper, response: :rock},
%{opponent: :scissors, response: :paper},
%{opponent: :scissors, response: :scissors},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :rock, response: :paper},
%{opponent: :paper, response: :rock},
%{opponent: :paper, response: :paper},
%{opponent: :paper, response: :paper},
%{opponent: :paper, ...},
%{...},
...
]
Day2.PartOne.solution()
10404
defmodule Day2.PartTwo do
@moduledoc """
Solution for Day 2 Part Two
"""
@typedoc """
Represents a move in the game of rock, paper, scissors
"""
@type move() :: :rock | :paper | :scissors
@typedoc """
Represents the result of a single round of rock, paper, scissors
"""
@type result() :: :win | :lose | :draw
@typedoc """
Represents a single round of the game rock, paper, scissors
"""
@type round() :: %{
opponent: move(),
response: move()
}
@typedoc """
Represents a strategy for a single round of the game rock, paper, scissors
"""
@type round_strategy() :: %{
opponent: move(),
expected_result: result()
}
@doc """
Parses a move consisting of "A", "B", "C", "X", "Y", "Z" into the corresponding
move of `:rock`, `:paper`, or `:scissors`
"""
@spec parse_move(String.t()) :: move()
def parse_move(move) do
case move do
"A" -> :rock
"B" -> :paper
"C" -> :scissors
end
end
@doc """
Parses an expected result consisting of "X", "Y", or "Z" into the corresponding
result of `:win`, `:lose`, or `:draw`
"""
@spec parse_expected_result(String.t()) :: result()
def parse_expected_result(move) do
case move do
"X" -> :lose
"Y" -> :draw
"Z" -> :win
end
end
@doc """
List of all round stategies
"""
@spec round_strategies() :: [round_strategy()]
def round_strategies() do
Utilities.read_data(2)
|> Stream.map(fn <> <> " " <> expected_result ->
%{
opponent: parse_move(opponent),
expected_result: parse_expected_result(expected_result)
}
end)
|> Enum.to_list()
end
@doc """
Judge the given round to determine if it is a win, loss, or draw for the player
"""
@spec judge_round(round()) :: result()
def judge_round(%{opponent: opponent, response: response}) do
case {opponent, response} do
{:rock, :rock} -> :draw
{:rock, :paper} -> :win
{:rock, :scissors} -> :lose
{:paper, :rock} -> :lose
{:paper, :paper} -> :draw
{:paper, :scissors} -> :win
{:scissors, :rock} -> :win
{:scissors, :paper} -> :lose
{:scissors, :scissors} -> :draw
end
end
@doc """
Score the round according to the given rubric that calculates a score based upon the
reponse alone plus a score from the round's result
"""
@spec score_round(round()) :: pos_integer()
def score_round(%{opponent: _, response: response} = round) do
response_score =
case response do
:rock -> 1
:paper -> 2
:scissors -> 3
end
outcome_score =
case judge_round(round) do
:win -> 6
:lose -> 0
:draw -> 3
end
response_score + outcome_score
end
@doc """
Convert a strategy to a round by computing which move is required to respond to
the opponent to guarantee the expected result
"""
@spec convert_strategy_to_round(round_strategy()) :: round()
def convert_strategy_to_round(round_strategy) do
response =
case {round_strategy.opponent, round_strategy.expected_result} do
{:rock, :win} -> :paper
{:rock, :lose} -> :scissors
{:paper, :win} -> :scissors
{:paper, :lose} -> :rock
{:scissors, :win} -> :rock
{:scissors, :lose} -> :paper
{move, :draw} -> move
end
%{opponent: round_strategy.opponent, response: response}
end
@doc """
A list of all the rounds' scores
"""
@spec scored_rounds_with_strategy() :: [pos_integer()]
def scored_rounds_with_strategy() do
round_strategies()
|> Enum.map(&convert_strategy_to_round/1)
|> Enum.map(&score_round/1)
end
def solution(), do: scored_rounds_with_strategy() |> Enum.sum()
end
{:module, Day2.PartTwo, <<70, 79, 82, 49, 0, 0, 24, ...>>, {:solution, 0}}
Day2.PartTwo.round_strategies()
[
%{expected_result: :draw, opponent: :paper},
%{expected_result: :win, opponent: :rock},
%{expected_result: :win, opponent: :scissors},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :scissors},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :win, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :draw, opponent: :scissors},
%{expected_result: :lose, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :scissors},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :scissors},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :lose, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :lose, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :scissors},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :win, opponent: :scissors},
%{expected_result: :lose, opponent: :rock},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :win, opponent: :rock},
%{expected_result: :lose, opponent: :paper},
%{expected_result: :draw, opponent: :scissors},
%{expected_result: :win, opponent: :scissors},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :rock},
%{expected_result: :lose, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :draw, opponent: :paper},
%{expected_result: :lose, ...},
%{...},
...
]
Day2.PartTwo.solution()
10334
Day 3
https://adventofcode.com/2022/day/3
defmodule Day3 do
@moduledoc """
Solutions for Day 3
"""
@typedoc """
Represents a single compartment
"""
@type compartment() :: charlist()
@typedoc """
Represents a rucksack which consists of two equally sized compartments
"""
@type rucksack() :: {
first_compartment :: compartment(),
second_compartment :: compartment()
}
@doc """
Parse a rucksack string into a rucksack 2-tuple for the two
compartments
"""
@spec parse_rucksack(String.t()) :: rucksack()
def parse_rucksack(data) do
# Note that compartments are specified to be of the same length
data
|> String.to_charlist()
|> Enum.split(div(String.length(data), 2))
end
@doc """
A list of all the rucksacks
"""
@spec rucksacks() :: [rucksack()]
def rucksacks() do
Utilities.read_data(3)
|> Enum.map(&parse_rucksack/1)
end
@doc """
Given a charlist of a single element, calculate its priority according to the
rubric of a -> z has priority 1 -> 26 and A -> Z has priority 27 -> 52
"""
@spec assign_item_priority(charlist()) :: pos_integer()
def assign_item_priority([char] = charlist) do
# Note that all codepoints after 'a' and 'A' are sequential up to 'z'
# and 'Z', respectively.
if Utilities.lowercase?(to_string(charlist)) do
# Assigns a -> z the priority 1 -> 26 by normalizing against the codepoint for 'a'
char - ?a + 1
else
# Assigns A -> Z the priority 27 -> 52 by normalizing against the codepoint for 'A'
char - ?A + 27
end
end
def part_one() do
rucksacks()
# Convert each rucksack's compartments into sets and then find their
# intersection and then assign a priority to that single element in
# the intersection
|> Enum.map(fn {first, second} ->
MapSet.intersection(MapSet.new(first), MapSet.new(second))
# Strip away the MapSet, leaving a charlist
|> MapSet.to_list()
|> assign_item_priority()
end)
# Sum up all the priorities, with one priority from each rucksack
|> Enum.sum()
end
def part_two() do
rucksacks()
# Chunk every three rucksacks into a group
|> Enum.chunk_every(3)
# Convert all rucksacks in the group to sets and find their intersection
# and then assign a priority to that single element in the intersection
|> Enum.map(fn chunk ->
chunk
|> Enum.map(fn {first, second} ->
(first ++ second)
|> MapSet.new()
end)
|> Enum.reduce(&MapSet.intersection/2)
# Strip away the MapSet, leaving a charlist
|> MapSet.to_list()
|> assign_item_priority()
end)
# Sum up all the priorities, with one priority from each group
|> Enum.sum()
end
end
{:module, Day3, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:part_two, 0}}
Day3.rucksacks()
[
{'WwcsbsWw', 'spmFTGVV'},
{'RHtMDHdSMnDBGMSDvnvDjt', 'mpTpjTFggpmjmTFggTjmpP'},
{'vtCSGRMBDzHddvBHBzR', 'hrlcZhlLzWNlqblhzcr'},
{'shhszHNHHZWqSzV', 'NdClMjlFjBBbNTB'},
{'tQQGmnrMnJnGfmvrRR', 'PCjlbljFBdjFCjTjnP'},
{'mRwtfGrMmJtwRDvQJQrJpM', 'LSzVDHzhzHZqZzqSzcWVWH'},
{'WsWWgrtgsrhTQtsFcWPc', 'RMCCTvqvMvqNNqMMHlMq'},
{'bBJrBGbzzLJznJrbSDGGJ', 'LqmlvqMqvlmLHRqRZZRNZ'},
{'bzJfDGVSzVrJGwjVG', 'PPpQthdPsPpjdphsc'},
{'pJpCCBSWlczWWBW', 'MHdMmMsFmpddrgF'},
{'wfVqZZGVQv', 'zsMqmMgHjm'},
{'vDZGvPttQTVtGDQDDDGw', 'bSCcSJSCJWTcRRSRczRJ'},
{'HLVHsVWLwbWswbpWFWrrmT', 'hfTPNnhNSDDNhDfznTnhnS'},
{'pBRcvGvvBtpGcqqQvgcp', 'hPfzfDGhzdzPDzDDhnhS'},
{'ZQRvqBptjJgZCtJqq', 'MMMLHWwMWZWHHFFHm'},
{'PvPFPvLLLSvNFvQNWNPvrPLr', 'ZjwhMttTwtTtQZBwqjqtZqwM'},
{'HJDDbHjgppzCDCmzpgzsGbCs', 'TMZqZllqhJBhMTtVBBhMtMth'},
{'zgGncmGGzHCnHDpDgDCGsm', 'FLLPFjPRRWLRjdcjrcdRLd'},
{'zHnWzntnBRWTSBzRBdd', 'pFvZVcHpLFvjvLppvHP'},
{'MmmWmNGQhbC', 'pZVLLbccvpj'},
{'QDMCGrNWfwN', 'znBJsJzDBdg'},
{'tcRcZccZmdZJctRcj', 'rlhNNDfrdNdSfNsNT'},
{'QHQpBVvMpRMwgBgvnHR', 'FlhrSsgNFThgTFFflNS'},
{'vvHpVBBBGBppHvpLvHG', 'bjmmtCqWLJJZRzZZZZb'},
{'ZBtTDZRWsTsDZVWVZD', 'mjpbLbpSSzmLpWrbrS'},
{'MFNNFvvwFHwlh', 'mNrCStLNtjzrb'},
{'vwffwcHwflGqGflHJf', 'DBBZtQVBgZQJtBBsnT'},
{'pTJcmMJTspmpMZZJJZ', 'HCQQMzPBlQdWWWFzWP'},
{'LDnwrdnDnqjfqgvfDjrf', 'FlBBPFHFSHPQCBvQSSWB'},
{'nLbjgLjdbrwV', 'RcppsscJVRRR'},
{'mHnfggmM', 'tpHPPBCs'},
{'PJjlQQRrJhJNPP', 'TtBsCbCCTlpptd'},
{'rSSDhNQwShRRjh', 'mMPmzMDfPmfLzL'},
{'HzLFBgrCthtFrrhFSCCCvB', 'QNRVmJJJmnpnddmppddVtJ'},
{'MPZsjDWPjZs', 'VzNTzpVdRdZ'},
{'qMfjWfwclsPsjwzq', 'HgLFhwGFwHrFFrSC'},
{'llllmSbhNmSbNzlPmRN', 'CcgLLchHHpTGsCTQGpT'},
{'dVjBrvBB', 'VLJQsLpC'},
{'frZBWBDMFnd', 'StFsSwzlPlq'},
{'vmTVVtmJHwCwDlltt', 'TsrcPcMrfqPMMpjMq'},
{'LQGBRgGGRNgGgBhgzH', 'fpjPqsMjpLcLjrPLpq'},
{'BdgzgSRGBnN', 'HJtJlVStVmt'},
{'FbDQsFjPVHFZFSbrVjSVv', 'MJlGBJhDcqBBllJGccJnh'},
{'RfTCTTpmppfgwCpwpLw', 'RMnMGMlcPGqhddPcJnl'},
{'zgLPLNCCpLggzmTzTWm', 'VrjVvrNvjjjvbVHQZZH'},
{'RBjjpwmRszB', 'dvhLdSvpVpV'},
{'GrbfbJWmQJGWrGZZQMb', 'SLggfCgSHhCSgShghSC'},
{'DWNDZQcrbWQrZJZGQQZ', 'PsztzBsPmBTzwcwRwjT'},
{'rlvgglvZqbrbWbWWdvdm', ...},
{...},
...
]
Day3.part_one()
7850
Day3.part_two()
2581
Day 4
https://adventofcode.com/2022/day/4
defmodule Day4 do
@moduledoc """
Solutions for Day 4
"""
@typedoc """
Represents a pairing of two elf's section assignments. The section assignments
are represented by Elixir `Range`s.
"""
@type assignment_pair() :: {Range.t(), Range.t()}
@doc """
Parse an assignment pair string into a tuple of section assignment ranges
## Examples:
iex> Day4.parse_assignment_pair("2-4,6-8")
{2..4, 6..8}
"""
@spec parse_assignment_pair(String.t()) :: assignment_pair()
def parse_assignment_pair(string) do
[[a_start, a_end], [b_start, b_end]] =
for range <- String.split(string, ",", trim: true) do
String.split(range, "-", trim: true)
|> Enum.map(&String.to_integer/1)
end
{Range.new(a_start, a_end), Range.new(b_start, b_end)}
end
@doc """
List of all the assignment pairs
"""
@spec assignment_pairs() :: [assignment_pair()]
def assignment_pairs() do
Utilities.read_data(4)
|> Enum.map(&parse_assignment_pair/1)
end
@doc """
Determines if range 1 is a subset of range 2
"""
@spec range_subset?(Range.t(), Range.t()) :: boolean()
def range_subset?(range1, range2) do
MapSet.subset?(MapSet.new(range1), MapSet.new(range2))
end
@doc """
Determines if one of the ranges is fully contained in (i.e., a subset of) the other
"""
@spec range_contained_in_the_other?(Range.t(), Range.t()) :: boolean()
def range_contained_in_the_other?(range1, range2) do
range_subset?(range1, range2) or range_subset?(range2, range1)
end
def part_one() do
assignment_pairs()
|> Enum.count(fn {range1, range2} -> range_contained_in_the_other?(range1, range2) end)
end
def part_two() do
assignment_pairs()
|> Enum.count(fn {range1, range2} -> !Range.disjoint?(range1, range2) end)
end
end
{:module, Day4, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:part_two, 0}}
Day4.part_one()
462
Day4.part_two()
835
Day 5
https://adventofcode.com/2022/day/5
defmodule Day5 do
@moduledoc """
Solutions for Day 5
"""
alias Stack
@typedoc """
Represents a crate's name or identifier as a string
"""
@type crate() :: String.t()
@typedoc """
Represents a single stack of crates
"""
@type stack() :: Stack.t(crate())
@typedoc """
Represents a stack's name
"""
@type stack_name() :: non_neg_integer()
@typedoc """
Represents an instruction that says which crate to move from what stack
to what stack
"""
@type move_instruction() :: %{
number_of_crates: non_neg_integer(),
from: stack_name(),
to: stack_name()
}
# @spec initial_stacks() :: [stack()]
# def initial_stacks() do
# [
# ["F", "T", "N", "Z", "M", "G", "H", "J"],
# ["J", "W", "V"],
# ["H", "T", "B", "J", "L", "V", "G"],
# ["L", "V", "D", "C", "N", "J", "P", "B"],
# ["G", "R", "P", "M", "S", "W", "F"],
# ["M", "V", "N", "B", "F", "C", "H", "G"],
# ["R", "M", "G", "H", "D"],
# ["D", "Z", "V", "M", "N", "H"],
# ["H", "F", "N", "G"]
# ]
# |> Enum.map(&Stack.new/1)
# end
@doc """
List of the initial stacks and their crates
"""
@spec initial_stacks() :: [stack()]
def initial_stacks() do
Utilities.read_data(5, trim: false)
# Get the stack data only, leaving off the instructions
|> Enum.take_while(fn s -> s != "\n" end)
# Chunk every line into "column" elements
|> Enum.map(fn line ->
line
|> String.codepoints()
|> Enum.chunk_every(4)
|> Enum.map(&Enum.join/1)
|> Enum.map(&String.trim/1)
end)
# Transpose the lines so as to turn each line into a single stack,
# with the first element of the list being the top stack element
|> Utilities.transpose()
|> Enum.map(fn crates ->
crates
# Drop the stack number from the end
|> Utilities.drop_last()
# Get rid of the "empty" crates on top of the real crates
|> Enum.filter(&Utilities.non_empty?/1)
# Parse the crates to just get the name
|> Enum.map(fn "[" <> <> <> "]" -> crate end)
end)
|> Enum.map(&Stack.new/1)
end
@doc """
Parse an assignment pair string into a tuple of section assignment ranges
## Examples:
iex> Day4.parse_assignment_pair("2-4,6-8")
{2..4, 6..8}
"""
@spec parse_move_instruction(String.t()) :: move_instruction()
def parse_move_instruction(string) do
["move", number, "from", from, "to", to] = String.split(string, " ", trim: true)
# The stack numbers 1-indexed in the problem but lists are 0-indexed in Elixir
%{
number_of_crates: String.to_integer(number),
from: String.to_integer(from) - 1,
to: String.to_integer(to) - 1
}
end
@doc """
List of all move instructions
"""
@spec move_instructions() :: [move_instruction()]
def move_instructions() do
Utilities.read_data(5)
|> Enum.drop_while(&Utilities.non_empty?/1)
|> Enum.map(&String.trim/1)
# This gets rid of any remaining empty lines
|> Enum.filter(&Utilities.non_empty?/1)
|> Enum.map(&parse_move_instruction/1)
end
@doc """
Handle a move instruction for the case where crates are moved one at a time
"""
@spec handle_move_instruction([stack()], move_instruction()) :: [stack()]
def handle_move_instruction(stacks, move_instruction) do
# Pop (really peek and drop) and push crates
1..move_instruction.number_of_crates
|> Enum.reduce(stacks, fn _, stacks ->
# Get the crate that will be moved by peeking it from the "from" stack
crate_to_move =
stacks
|> Enum.at(move_instruction.from)
|> Stack.peek()
# Drop the crate from the "from" stack and push the already peeked
# crate to the "to" stack
stacks
|> List.update_at(move_instruction.from, &Stack.drop/1)
|> List.update_at(move_instruction.to, &Stack.push(&1, crate_to_move))
end)
end
@doc """
Handle a move instruction for the case where multiple crates are moved at once
"""
@spec handle_move_instruction_in_order([stack()], move_instruction()) :: [stack()]
def handle_move_instruction_in_order(stacks, move_instruction) do
# Pop (really peek and drop) and push crates
crates_to_move =
stacks
|> Enum.at(move_instruction.from)
|> Stack.peek(move_instruction.number_of_crates)
stacks
|> List.update_at(
move_instruction.from,
&Stack.drop(&1, move_instruction.number_of_crates)
)
|> List.update_at(move_instruction.to, &Stack.push_as_group(&1, crates_to_move))
end
def part_one() do
move_instructions()
|> Enum.reduce(initial_stacks(), fn move_instruction, stacks ->
handle_move_instruction(stacks, move_instruction)
end)
# Peek the top of all the final stacks
|> Enum.map(&Stack.peek/1)
|> Enum.join()
end
def part_two() do
move_instructions()
|> Enum.reduce(initial_stacks(), fn move_instruction, stacks ->
handle_move_instruction_in_order(stacks, move_instruction)
end)
# Peek the top of all the final stacks
|> Enum.map(&Stack.peek/1)
|> Enum.join()
end
end
{:module, Day5, <<70, 79, 82, 49, 0, 0, 31, ...>>, {:part_two, 0}}
Day5.initial_stacks()
[
%Stack{elements: ["F", "T", "N", "Z", "M", "G", "H", "J"]},
%Stack{elements: ["J", "W", "V"]},
%Stack{elements: ["H", "T", "B", "J", "L", "V", "G"]},
%Stack{elements: ["L", "V", "D", "C", "N", "J", "P", "B"]},
%Stack{elements: ["G", "R", "P", "M", "S", "W", "F"]},
%Stack{elements: ["M", "V", "N", "B", "F", "C", "H", "G"]},
%Stack{elements: ["R", "M", "G", "H", "D"]},
%Stack{elements: ["D", "Z", "V", "M", "N", "H"]},
%Stack{elements: ["H", "F", "N", "G"]}
]
Day5.move_instructions()
[
%{from: 3, number_of_crates: 6, to: 2},
%{from: 7, number_of_crates: 5, to: 8},
%{from: 3, number_of_crates: 1, to: 4},
%{from: 3, number_of_crates: 1, to: 4},
%{from: 1, number_of_crates: 2, to: 6},
%{from: 0, number_of_crates: 2, to: 5},
%{from: 5, number_of_crates: 9, to: 0},
%{from: 2, number_of_crates: 12, to: 4},
%{from: 7, number_of_crates: 1, to: 3},
%{from: 0, number_of_crates: 3, to: 4},
%{from: 5, number_of_crates: 1, to: 6},
%{from: 4, number_of_crates: 10, to: 1},
%{from: 4, number_of_crates: 14, to: 0},
%{from: 6, number_of_crates: 8, to: 8},
%{from: 1, number_of_crates: 11, to: 8},
%{from: 2, number_of_crates: 1, to: 8},
%{from: 0, number_of_crates: 11, to: 4},
%{from: 0, number_of_crates: 2, to: 8},
%{from: 3, number_of_crates: 1, to: 7},
%{from: 0, number_of_crates: 6, to: 4},
%{from: 7, number_of_crates: 1, to: 2},
%{from: 4, number_of_crates: 16, to: 0},
%{from: 0, number_of_crates: 4, to: 2},
%{from: 4, number_of_crates: 1, to: 5},
%{from: 2, number_of_crates: 4, to: 3},
%{from: 5, number_of_crates: 1, to: 6},
%{from: 8, number_of_crates: 21, to: 5},
%{from: 0, number_of_crates: 2, to: 8},
%{from: 3, number_of_crates: 2, to: 8},
%{from: 8, number_of_crates: 5, to: 3},
%{from: 0, number_of_crates: 9, to: 5},
%{from: 3, number_of_crates: 6, to: 5},
%{from: 5, number_of_crates: 1, to: 1},
%{from: 6, number_of_crates: 1, to: 5},
%{from: 2, number_of_crates: 1, to: 1},
%{from: 5, number_of_crates: 8, to: 8},
%{from: 0, number_of_crates: 3, to: 7},
%{from: 1, number_of_crates: 1, to: 0},
%{from: 5, number_of_crates: 13, to: 2},
%{from: 0, number_of_crates: 1, to: 8},
%{from: 0, number_of_crates: 2, to: 5},
%{from: 7, number_of_crates: 3, to: 3},
%{from: 3, number_of_crates: 4, to: 8},
%{from: 0, number_of_crates: 3, to: 2},
%{from: 8, number_of_crates: 22, to: 7},
%{from: 1, number_of_crates: 1, to: 8},
%{from: 7, number_of_crates: 6, to: 8},
%{from: 5, number_of_crates: 15, ...},
%{from: 7, ...},
%{...},
...
]
Day5.handle_move_instruction(Day5.initial_stacks(), %{from: 4, number_of_crates: 6, to: 3})
[
%Stack{elements: ["F", "T", "N", "Z", "M", "G", "H", "J"]},
%Stack{elements: ["J", "W", "V"]},
%Stack{elements: ["H", "T", "B", "J", "L", "V", "G"]},
%Stack{elements: ["W", "S", "M", "P", "R", "G", "L", "V", "D", "C", "N", "J", "P", "B"]},
%Stack{elements: ["F"]},
%Stack{elements: ["M", "V", "N", "B", "F", "C", "H", "G"]},
%Stack{elements: ["R", "M", "G", "H", "D"]},
%Stack{elements: ["D", "Z", "V", "M", "N", "H"]},
%Stack{elements: ["H", "F", "N", "G"]}
]
Day5.part_one()
"TDCHVHJTG"
Day5.part_two()
"NGCMPJLHV"
Day 6
https://adventofcode.com/2022/day/6
defmodule Day6 do
@moduledoc """
Solutions to Day 6
"""
@doc """
List of all the incoming data
"""
@spec data_stream() :: [String.t()]
def data_stream() do
Utilities.read_data(6)
|> Enum.to_list()
|> hd()
|> String.trim()
|> String.codepoints()
end
@doc """
Finds the index of the next element after the first window of the given size
that contains unique elements
"""
@spec find_end_of_first_distinct_window([any()], pos_integer()) :: pos_integer()
def find_end_of_first_distinct_window(data_stream, window_size) do
data_stream
|> Enum.chunk_every(window_size, 1)
|> Enum.find_index(&Utilities.unique_elements?/1)
|> Kernel.+(window_size)
end
def part_one() do
data_stream()
|> find_end_of_first_distinct_window(4)
end
def part_two() do
data_stream()
|> find_end_of_first_distinct_window(14)
end
end
{:module, Day6, <<70, 79, 82, 49, 0, 0, 11, ...>>, {:part_two, 0}}
Day6.data_stream()
|> Enum.chunk_every(4, 1)
[
["b", "g", "d", "b"],
["g", "d", "b", "d"],
["d", "b", "d", "s"],
["b", "d", "s", "b"],
["d", "s", "b", "s"],
["s", "b", "s", "b"],
["b", "s", "b", "s"],
["s", "b", "s", "t"],
["b", "s", "t", "t"],
["s", "t", "t", "l"],
["t", "t", "l", "d"],
["t", "l", "d", "d"],
["l", "d", "d", "d"],
["d", "d", "d", "z"],
["d", "d", "z", "z"],
["d", "z", "z", "w"],
["z", "z", "w", "n"],
["z", "w", "n", "z"],
["w", "n", "z", "z"],
["n", "z", "z", "m"],
["z", "z", "m", "p"],
["z", "m", "p", "z"],
["m", "p", "z", "m"],
["p", "z", "m", "m"],
["z", "m", "m", "z"],
["m", "m", "z", "m"],
["m", "z", "m", "q"],
["z", "m", "q", "q"],
["m", "q", "q", "c"],
["q", "q", "c", "g"],
["q", "c", "g", "g"],
["c", "g", "g", "l"],
["g", "g", "l", "r"],
["g", "l", "r", "g"],
["l", "r", "g", "l"],
["r", "g", "l", "g"],
["g", "l", "g", "b"],
["l", "g", "b", "b"],
["g", "b", "b", "b"],
["b", "b", "b", "t"],
["b", "b", "t", "m"],
["b", "t", "m", "t"],
["t", "m", "t", "d"],
["m", "t", "d", "d"],
["t", "d", "d", "r"],
["d", "d", "r", "s"],
["d", "r", "s", ...],
["r", "s", ...],
["s", ...],
[...],
...
]
Day6.part_one()
1794
Day6.part_two()
2851
Day 7
https://adventofcode.com/2022/day/7
defmodule Day7 do
@moduledoc """
Solutions to Day 7
"""
@type structured_input() ::
{:command, :change_directory, String.t()}
| {:command, :list}
| {:directory, name :: String.t()}
| {:file, name :: String.t(), size :: integer()}
@type directory_tree() ::
{:directory, name :: String.t(), children :: directory_tree()}
| {:file, name :: String.t(), size :: integer()}
@spec parse_input(String.t()) :: structured_input()
def parse_input("$ cd " <> directory) do
{:command, :change_directory, directory}
end
def parse_input("$ ls") do
{:command, :list}
end
def parse_input("dir " <> name) do
{:directory, name}
end
def parse_input(file) do
[size, name] = String.split(file, " ", trim: true)
{:file, name, String.to_integer(size)}
end
@example_filesystem %{
/: %{
a: %{
e: %{
i: 584
},
f: 29116,
g: 2557,
"h.lst": 62596
},
"b.txt": 14_848_514,
"c.dat": 8_504_156,
d: %{
j: 4_060_174,
"d.log": 8_033_020,
"d.ext": 5_626_152,
k: 7_214_296
}
}
}
def calculate_directory_size(%{/: children} = filesystem) do
Map.put(filesystem, :size, children |> Enum.map(&calculate_directory_size/1) |> Enum.sum())
end
# def calculate_directory_size(%{filename => size}) when is_atom(filename) and is_integer(size) do
# size
# end
# def calculate_directory_size(%{directory => children})
# when is_atom(directory) and is_map(children) do
# %{
# directory => children,
# size: children |> Enum.map(&calculate_directory_size/1) |> Enum.sum()
# }
# end
@doc """
List of all the incoming data
"""
@spec inputs() :: [structured_input()]
def inputs() do
Utilities.read_data(7)
|> Stream.map(&parse_input/1)
|> Enum.to_list()
end
def get_directory_access(base_directory, directory) do
base_directory
|> Path.join(directory)
|> Path.split()
end
def filesystem_builder({:command, :change_directory, "/"}, {_current_directory, filesystem}) do
{"/", filesystem}
end
def filesystem_builder({:command, :change_directory, ".."}, {current_directory, filesystem}) do
{Path.dirname(current_directory), filesystem}
end
def filesystem_builder(
{:command, :change_directory, directory},
{current_directory, filesystem}
) do
{Path.join(current_directory, directory), filesystem}
end
def filesystem_builder({:command, :list}, state) do
state
end
def filesystem_builder({:directory, name}, {current_directory, filesystem}) do
IO.inspect(current_directory, label: "current directory")
IO.inspect(filesystem, label: "filesystem")
{current_directory, put_in(filesystem, get_directory_access(current_directory, name), %{})}
end
def filesystem_builder({:file, name, size}, {current_directory, filesystem}) do
new_filesystem =
filesystem
|> put_in(get_directory_access(current_directory, name), size)
{current_directory, new_filesystem}
end
def build_filesystem() do
inputs()
|> List.foldl({nil, %{"/" => nil}}, &filesystem_builder/2)
end
def part_one() do
2
end
def part_two() do
2
end
end
warning: module attribute @example_filesystem was set but never used
Documents/GitHub/advent-of-code/2022/elixir/advent_of_code_2022.livemd#cell:j6ujhu4gs6hshx7iax2bwa5q7btykmwe:34
{:module, Day7, <<70, 79, 82, 49, 0, 0, 22, ...>>, {:part_two, 0}}
Enum.map(%{a: 1, b: 2}, fn x -> x end)
[a: 1, b: 2]
Day7.inputs()
[
{:command, :change_directory, "/"},
{:command, :list},
{:directory, "ccjp"},
{:file, "hglnvs.bsh", 328708},
{:directory, "hpsnpc"},
{:directory, "pcb"},
{:directory, "pntzm"},
{:directory, "pzg"},
{:directory, "thfgwwsp"},
{:command, :change_directory, "ccjp"},
{:command, :list},
{:file, "dlz", 159990},
{:directory, "mbtsvblj"},
{:file, "nppbjl.qhg", 165076},
{:command, :change_directory, "mbtsvblj"},
{:command, :list},
{:file, "frqsf.nsv", 34806},
{:directory, "ppq"},
{:directory, "ptht"},
{:directory, "rgmvdwt"},
{:command, :change_directory, "ppq"},
{:command, :list},
{:file, "dhzp", 266252},
{:command, :change_directory, ".."},
{:command, :change_directory, "ptht"},
{:command, :list},
{:directory, "jbnj"},
{:directory, "zcbnwhzd"},
{:command, :change_directory, "jbnj"},
{:command, :list},
{:directory, "clscr"},
{:file, "zwtwf.zfz", 145780},
{:command, :change_directory, "clscr"},
{:command, :list},
{:file, "dhzp", 249531},
{:command, :change_directory, ".."},
{:command, :change_directory, ".."},
{:command, :change_directory, "zcbnwhzd"},
{:command, :list},
{:directory, "mbtsvblj"},
{:command, :change_directory, "mbtsvblj"},
{:command, :list},
{:file, "pjhvzjt.brz", 258527},
{:command, :change_directory, ".."},
{:command, :change_directory, ".."},
{:command, :change_directory, ".."},
{:command, :change_directory, "rgmvdwt"},
{:command, :list},
{:file, ...},
{...},
...
]
Day7.get_directory_access("/", "ccjp")
["/", "ccjp"]
put_in(%{}, ["ccjp"], %{"/" => nil})
%{"ccjp" => %{"/" => nil}}
Day7.build_filesystem()
current directory: "/"
filesystem: %{"/" => nil}
Day7.part_one()
2
Day7.part_two()
2
Tests
Writing tests for the solutions is important to re-verify solutions for changes that occur after a solution is first submitted and verified as correct. This ensures the solutions stay correct after refactors.
ExUnit.start(autorun: false)
defmodule AdventOfCode.Tests do
use ExUnit.Case, async: true
test "Day 1" do
assert Day1.part_one() == 71_780
assert Day1.part_two() == 212_489
end
test "Day 2" do
assert Day2.PartOne.solution() == 10_404
assert Day2.PartTwo.solution() == 10_334
end
test "Day 3" do
assert Day3.part_one() == 7850
assert Day3.part_two() == 2581
end
test "Day 4" do
assert Day4.part_one() == 462
assert Day4.part_two() == 835
end
test "Day 5" do
assert Day5.part_one() == "TDCHVHJTG"
assert Day5.part_two() == "NGCMPJLHV"
end
test "Day 6" do
assert Day6.part_one() == 1794
assert Day6.part_two() == 2851
end
test "Day 7" do
assert Day7.part_one() == 1794
assert Day7.part_two() == 2851
end
end
ExUnit.run()
......
1) test Day 7 (AdventOfCode.Tests)
Documents/GitHub/advent-of-code/2022/elixir/advent_of_code_2022.livemd#cell:e5gr53niynkx7csnbjw53hy2qzhgy7uq:36
Assertion with == failed
code: assert Day7.part_one() == 1794
left: 2
right: 1794
stacktrace:
Documents/GitHub/advent-of-code/2022/elixir/advent_of_code_2022.livemd#cell:e5gr53niynkx7csnbjw53hy2qzhgy7uq:37: (test)
Finished in 0.05 seconds (0.05s async, 0.00s sync)
7 tests, 1 failure
Randomized with seed 187784
%{excluded: 0, failures: 1, skipped: 0, total: 7}