Created
October 24, 2019 12:43
-
-
Save gungorkocak/656d8308769a7d409bbb6677243ae782 to your computer and use it in GitHub Desktop.
Various Days from Advent of Code Challenge 2018 / with Elixir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Day11 do | |
@ets_table_name :day11_max_powers | |
@grid {300, 300} | |
@doc """ | |
Initializes required partial sum caching mechanism (ets table). | |
""" | |
def start() do | |
:ets.new(@ets_table_name, [:set, :named_table, read_concurrency: true]) | |
end | |
defp lookup({x, y}) do | |
:ets.lookup(@ets_table_name, {x, y}) | |
end | |
defp insert({x, y}, {size, power}) do | |
:ets.insert(@ets_table_name, {{x, y}, {size, power}}) | |
end | |
@doc """ | |
Calculates total power level based on grid position and serial number. | |
## Examples | |
iex> Day11.power_level({3, 5}, 8) | |
4 | |
iex> Day11.power_level({122, 79}, 57) | |
-5 | |
iex> Day11.power_level({217, 196}, 39) | |
0 | |
iex> Day11.power_level({101, 153}, 71) | |
4 | |
""" | |
@spec power_level({integer, integer}, integer) :: integer | |
def power_level({x, y}, serial_num) do | |
rack_id = x + 10 | |
raw = (rack_id * y + serial_num) * rack_id | |
third_digit = Integer.digits(raw) |> Enum.reverse() |> Enum.at(2) | |
third_digit - 5 | |
end | |
@doc """ | |
Calculates total power of a grid specified by its top left {x, y}, serial_num and size. | |
## Examples | |
iex> Day11.power_grid({33, 45}, 18, 3) | |
29 | |
iex> Day11.power_grid({21, 61}, 42, 3) | |
30 | |
""" | |
@spec power_grid({integer, integer}, integer, integer) :: integer | |
def power_grid({x, y}, serial_num, 1) do | |
power_level({x, y}, serial_num) | |
end | |
def power_grid({x, y}, serial_num, size) do | |
case lookup({x, y}) do | |
[] -> | |
pow = | |
power_grid({x, y}, serial_num, size - 1) + power_grid_outliers({x, y}, serial_num, size) | |
insert({x, y}, {size, pow}) | |
pow | |
[{_, {^size, pow}}] -> | |
pow | |
[{_, {prev_size, prev_pow}}] when prev_size == size - 1 -> | |
pow = prev_pow + power_grid_outliers({x, y}, serial_num, size) | |
insert({x, y}, {size, pow}) | |
pow | |
end | |
end | |
defp power_grid_outliers({x, y}, serial_num, size) do | |
x_sum = | |
Enum.reduce(x..(x + size - 1), 0, fn x, sum -> | |
sum + power_level({x, y + size - 1}, serial_num) | |
end) | |
y_sum = | |
Enum.reduce(y..(y + size - 2), 0, fn y, sum -> | |
sum + power_level({x + size - 1, y}, serial_num) | |
end) | |
x_sum + y_sum | |
end | |
@doc """ | |
Calculates largest total sizexsize subgrid sum then returns sum and {top, left} position of it. | |
## Examples | |
iex> Day11.max_power_grid(42, 3) | |
{{21, 61}, 30} | |
iex> Day11.max_power_grid(18, 3) | |
{{33, 45}, 29} | |
""" | |
@spec max_power_grid(integer, integer) :: {{integer, integer}, integer} | |
def max_power_grid(serial_num, size) do | |
{x, y} = @grid | |
reduce_grid({1..(x - size), 1..(y - size)}, {{nil, nil}, 0}, fn {x, y}, {max_pos, max_pow} -> | |
case power_grid({x, y}, serial_num, size) do | |
pow when pow > max_pow -> {{x, y}, pow} | |
_ -> {max_pos, max_pow} | |
end | |
end) | |
end | |
@doc """ | |
Finds and calculates largest total subgrid then returns size and {top, left} position it. | |
## Examples | |
# # This is taking too long. Couln't find a proper way to speed it up yet :/ | |
# iex> Day11.max_power_grid_of_all(42) | |
# {{232, 251}, 119, 12} | |
""" | |
@spec max_power_grid_of_all(integer) :: {{integer, integer}, integer, integer} | |
def max_power_grid_of_all(serial_num) do | |
{count, _} = @grid | |
Enum.reduce(1..count, {{nil, nil}, 0, 0}, fn size, {max_pos, max_pow, max_size} -> | |
IO.puts("Handling size... #{size}") | |
case max_power_grid(serial_num, size) do | |
{{x, y}, pow} when pow > max_pow -> | |
{{x, y}, pow, size} | |
_ -> | |
{max_pos, max_pow, max_size} | |
end | |
end) | |
end | |
defp reduce_grid({minx..maxx, miny..maxy}, acc, fun) do | |
Enum.reduce(minx..maxx, acc, fn x, acc -> | |
Enum.reduce(miny..maxy, acc, fn y, acc -> | |
fun.({x, y}, acc) | |
end) | |
end) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Day12 do | |
@initial_state "##.#....#..#......#..######..#.####.....#......##.##.##...#..#....#.#.##..##.##.#.#..#.#....#.#..#.#" | |
@doc """ | |
Resolves part#1. | |
""" | |
@spec index_sum_after_iteration(pos_integer) :: pos_integer | |
def index_sum_after_iteration(iteration \\ 20) do | |
last_state = iterate(@initial_state, iteration) | |
to_sum(last_state) | |
end | |
@doc """ | |
Resolves part#2. | |
""" | |
@spec sum_of_very_far_away_generation(pos_integer) :: integer | |
def sum_of_very_far_away_generation(very_far_away_iteration) do | |
{eq_iteration, sum, diff} = find_equilibrium_iteration() | |
sum + (very_far_away_iteration - eq_iteration + 1) * diff | |
end | |
@doc """ | |
Finds the generation which incrementation stabilizes for the last 10 generations. | |
Returns {generation, sum, diff} | |
""" | |
@spec find_equilibrium_iteration() :: {pos_integer, integer, integer} | |
def find_equilibrium_iteration() do | |
find_equilibrium_iteration(@initial_state, 1, [:infinity]) | |
end | |
@max_iterations 10_000 | |
defp find_equilibrium_iteration(current_state, iteration, diffs) do | |
next_state = iterate(current_state, 1) | |
next_sum = to_sum(next_state) | |
current_sum = to_sum(current_state) | |
next_diff = next_sum - current_sum | |
cond do | |
Enum.all?(diffs, fn diff -> diff == next_diff end) -> | |
{iteration + 1, next_sum, next_diff} | |
iteration == @max_iterations -> | |
{:error, "max iterations reached"} | |
true -> | |
next_diffs = Enum.slice([next_diff | diffs], 0..9) | |
find_equilibrium_iteration(next_state, iteration + 1, next_diffs) | |
end | |
end | |
@doc """ | |
Takes current state string, returns next state (generation) by applying rules to each element. | |
## Examples | |
iex> Day12.iterate("##.#.", 1) | |
".##.#...." | |
""" | |
@spec iterate(String.t(), integer) :: String.t() | |
def iterate(current_state, 0) do | |
current_state | |
end | |
def iterate(current_state, count) do | |
positives = positives_to_set(current_state) | |
next_state = | |
(".." <> current_state <> "..") | |
|> String.graphemes() | |
|> Enum.map(&to_plant_boolean/1) | |
|> Enum.with_index() | |
|> Enum.map(&plant_grows?(&1, positives)) | |
|> Enum.map(&to_plant_string/1) | |
|> Enum.join() | |
iterate(next_state, count - 1) | |
end | |
@doc """ | |
Calculates sum of all positive indexes from plant string. | |
## Examples | |
iex> Day12.to_sum("##.#....#") | |
12 | |
""" | |
@spec to_sum(String.t()) :: integer | |
def to_sum(string) do | |
string | |
|> positives_to_set() | |
|> Enum.sum() | |
end | |
@doc """ | |
Transforms string state into positive MapSet. Starts from 0 index. | |
iex> Day12.positives_to_set("##.#....#") | |
#MapSet<[0, 1, 3, 8]> | |
""" | |
@spec positives_to_set(String.t()) :: MapSet.t() | |
def positives_to_set(string) do | |
string | |
|> String.graphemes() | |
|> Enum.with_index() | |
|> Enum.reduce(MapSet.new(), fn | |
{".", _}, acc -> acc | |
{"#", index}, acc -> MapSet.put(acc, index) | |
end) | |
end | |
defp to_plant_boolean("#"), do: true | |
defp to_plant_boolean(_), do: false | |
defp to_plant_string(true), do: "#" | |
defp to_plant_string(_), do: "." | |
defp plant_grows?({_, index}, positives) do | |
(index - 2)..(index + 2) | |
|> Enum.map(&MapSet.member?(positives, &1)) | |
|> apply_rules() | |
end | |
@doc """ | |
Evalutes item in the middle by checking left and right 2 items, | |
then returns if whether the item will be on the next generation or not. | |
## Current Ruleset (raw): | |
##.# | |
"#####" => "#", | |
".#..#" => "#", | |
"#..#." => "#", | |
"#...#" => "#", | |
"...##" => "#", | |
"##..#" => "#", | |
".#.##" => "#", | |
"#.###" => "#", | |
".##.#" => "#", | |
".#..." => "#", | |
"##..." => "#", | |
"##.##" => "#", | |
"##.#." => "#", | |
"#.##." => "#" | |
... all other combinations are "." | |
""" | |
@spec apply_rules([boolean]) :: boolean | |
def apply_rules([true, true, true, true, true]), do: true | |
def apply_rules([false, true, false, false, true]), do: true | |
def apply_rules([true, false, false, true, false]), do: true | |
def apply_rules([true, false, false, false, true]), do: true | |
def apply_rules([false, false, false, true, true]), do: true | |
def apply_rules([true, true, false, false, true]), do: true | |
def apply_rules([false, true, false, true, true]), do: true | |
def apply_rules([true, false, true, true, true]), do: true | |
def apply_rules([false, true, true, false, true]), do: true | |
def apply_rules([false, true, false, false, false]), do: true | |
def apply_rules([true, true, false, false, false]), do: true | |
def apply_rules([true, true, false, true, true]), do: true | |
def apply_rules([true, true, false, true, false]), do: true | |
def apply_rules([true, false, true, true, false]), do: true | |
def apply_rules(_), do: false | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ChronalCalibration do | |
def calc_result(stream), do: Enum.sum(stream) | |
def find_first_redundant(stream) do | |
Stream.cycle(stream) | |
|> Stream.scan({0, MapSet.new([0])}, &collect_step/2) | |
|> Stream.filter(&redundant?/1) | |
|> Stream.map(fn {elem, _} -> elem end) | |
|> Enum.take(1) | |
|> Enum.at(0) | |
end | |
def file_to_stream!(file) do | |
File.stream!(file) | |
|> Stream.map(&String.trim(&1, "\n")) | |
|> Stream.map(&parse_frequency/1) | |
end | |
defp parse_frequency(""), do: 0 | |
defp parse_frequency(str) when is_binary(str), do: String.to_integer(str) | |
defp parse_frequency(_), do: 0 | |
defp collect_step(elem, {last, steps}), do: {elem + last, MapSet.put(steps, last)} | |
defp redundant?({last, steps}), do: MapSet.member?(steps, last) | |
defp redundant?(_), do: false | |
end | |
test = fn -> | |
defmodule ChronalCalibrationTest do | |
use ExUnit.Case, async: true | |
describe "part#1 frequency" do | |
test "results in 3" do | |
set = [+1, +1, +1] | |
assert 3 == ChronalCalibration.calc_result(set) | |
end | |
test "results in 0" do | |
set = [+1, +1, -2] | |
assert 0 == ChronalCalibration.calc_result(set) | |
end | |
test "results in -6" do | |
set = [-1, -2, -3] | |
assert -6 == ChronalCalibration.calc_result(set) | |
end | |
end | |
describe "part#2 first frequency" do | |
test "reaches 0 twice" do | |
set = [+1, -1] | |
assert 0 == ChronalCalibration.find_first_redundant(set) | |
end | |
test "reaches 10 twice" do | |
set = [+3, +3, +4, -2, -4] | |
assert 10 == ChronalCalibration.find_first_redundant(set) | |
end | |
test "reaches 5 twice" do | |
set = [-6, +3, +8, +5, -6] | |
assert 5 == ChronalCalibration.find_first_redundant(set) | |
end | |
test "reaches 14 twice" do | |
set = [+7, +7, -2, -7, -4] | |
assert 14 == ChronalCalibration.find_first_redundant(set) | |
end | |
end | |
end | |
end | |
case System.argv() do | |
["--test"] -> | |
ExUnit.start() | |
test.() | |
_ -> | |
set = ChronalCalibration.file_to_stream!("./1_input.txt") | |
result = ChronalCalibration.calc_result(set) | |
IO.puts("\n Frequency result is... #{result}") | |
redundant = ChronalCalibration.find_first_redundant(set) | |
IO.puts("\n Redundant frequency is... #{redundant} \n") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule InventoryManagement.Checksum do | |
def checksum(stream) do | |
stream | |
|> Stream.map(&String.trim/1) | |
|> Stream.map(&count_occurances/1) | |
|> Enum.reduce([two: 0, three: 0], &collect_each/2) | |
|> Enum.reduce(1, fn {_, val}, acc -> val * acc end) | |
end | |
defp collect_each([two: item_two, three: item_three], two: two, three: three) do | |
[two: two + item_two, three: three + item_three] | |
end | |
defp count_occurances(id) do | |
id | |
|> String.graphemes() | |
|> Enum.reduce(%{}, &count_letters/2) | |
|> Enum.reduce([two: 0, three: 0], &calc_valids/2) | |
end | |
defp count_letters(letter, acc), do: Map.update(acc, letter, 1, &(&1 + 1)) | |
defp calc_valids({_, count}, two: two, three: three) do | |
case count do | |
3 -> | |
[two: two, three: 1] | |
2 -> | |
[two: 1, three: three] | |
_ -> | |
[two: two, three: three] | |
end | |
end | |
end | |
defmodule InventoryManagement.PrototypeFabric do | |
def find(stream) do | |
stream | |
|> Stream.map(&String.trim/1) | |
|> Enum.reduce_while(MapSet.new(), &collect_combinations/2) | |
end | |
defp collect_combinations(id, combinations) do | |
id_combs = make_id_combs(id) | |
case Enum.find(id_combs, &MapSet.member?(combinations, &1)) do | |
comb when is_binary(comb) -> | |
{:halt, comb} | |
nil -> | |
combinations = Enum.reduce(id_combs, combinations, &MapSet.put(&2, &1)) | |
{:cont, combinations} | |
end | |
end | |
defp make_id_combs(id) do | |
for i <- 0..(String.length(id) - 1) do | |
for j <- 0..(String.length(id) - 1), j != i, into: "", do: String.at(id, j) | |
end | |
end | |
end | |
test = fn -> | |
defmodule InventoryManagementTest do | |
use ExUnit.Case, async: true | |
describe "part#1 checksum" do | |
test "calculated correctly with example input" do | |
example = | |
""" | |
abcdef | |
bababc | |
abbcde | |
abcccd | |
aabcdd | |
abcdee | |
ababab | |
""" | |
|> String.splitter("\n", trim: true) | |
assert 12 == InventoryManagement.Checksum.checksum(example) | |
end | |
end | |
describe "part#2 find prototype fabric" do | |
test "for example input" do | |
example = | |
""" | |
abcde | |
fghij | |
klmno | |
pqrst | |
fguij | |
axcye | |
wvxyz | |
""" | |
|> String.splitter("\n", trim: true) | |
assert "fgij" == InventoryManagement.PrototypeFabric.find(example) | |
end | |
end | |
end | |
end | |
case System.argv() do | |
["--test"] -> | |
ExUnit.start() | |
test.() | |
_ -> | |
checksum = | |
File.stream!("./2_inventory_management_input.txt") | |
|> InventoryManagement.Checksum.checksum() | |
IO.puts("Checksum result of the input file is... #{checksum}") | |
common_letters = | |
File.stream!("./2_inventory_management_input.txt") | |
|> InventoryManagement.PrototypeFabric.find() | |
IO.puts("Common letters for prototype fabric is... #{common_letters}") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule SpoilsOfFabric do | |
def count_overlaps(stream) do | |
stream | |
|> Stream.map(&cleanup/1) | |
|> Stream.map(&to_grid_positions/1) | |
|> Enum.reduce(%{}, &count_positions/2) | |
|> Enum.reduce(0, &count_overlaps/2) | |
end | |
def detect_sacred_fabric(stream) do | |
position_map = | |
stream | |
|> Stream.map(&cleanup/1) | |
|> Stream.map(&to_grid_positions/1) | |
|> Enum.reduce(%{}, &count_positions/2) | |
stream | |
|> Stream.map(&to_claim_tuple/1) | |
|> Stream.filter(&only_sacred_fabric(&1, position_map)) | |
|> Enum.take(1) | |
|> Enum.at(0) | |
|> get_id_from_claim_tuple() | |
end | |
def to_grid_positions([_, _, position, dimension]) do | |
[{x, _}, {y, _}] = | |
position | |
|> String.slice(0..-2) | |
|> String.split(",") | |
|> Enum.map(&Integer.parse/1) | |
[{w, _}, {h, _}] = | |
dimension | |
|> String.split("x") | |
|> Enum.map(&Integer.parse/1) | |
for i <- 1..w, j <- 1..h, do: "#{x + i},#{y + j}" | |
end | |
defp only_sacred_fabric({_claim, grid_pos}, position_map) do | |
Enum.all?(grid_pos, fn pos -> Map.get(position_map, pos, 0) < 2 end) | |
end | |
defp get_id_from_claim_tuple({claim, _}) do | |
claim |> cleanup |> Enum.at(0) |> String.slice(1..-1) | |
end | |
defp to_claim_tuple(claim), do: {claim, claim |> cleanup() |> to_grid_positions()} | |
defp cleanup(str), do: str |> String.trim() |> String.split(" ") | |
defp count_overlaps({_, count}, acc) when count >= 2, do: acc + 1 | |
defp count_overlaps(_, acc), do: acc | |
defp count_positions(grid_positions, position_map) do | |
grid_positions | |
|> Enum.reduce(position_map, &update_pos_map/2) | |
end | |
defp update_pos_map(pos, map) do | |
{_, map} = Map.get_and_update(map, pos, fn val -> {val, (val || 0) + 1} end) | |
map | |
end | |
end | |
test = fn -> | |
defmodule SpoilsOfFabricTest do | |
use ExUnit.Case, async: true | |
describe "part#1" do | |
test "gives proper overlap grid" do | |
example = ["#1", "@", "1,3:", "2x2"] | |
assert SpoilsOfFabric.to_grid_positions(example) == ["2,4", "2,5", "3,4", "3,5"] | |
end | |
test "counts overlap with example input" do | |
example = | |
""" | |
#1 @ 1,3: 4x4 | |
#2 @ 3,1: 4x4 | |
#3 @ 5,5: 2x2 | |
""" | |
|> String.splitter("\n", trim: true) | |
assert 4 == SpoilsOfFabric.count_overlaps(example) | |
end | |
test "counts overlap with more complex example input" do | |
example = | |
""" | |
#1 @ 111,41: 5x5 | |
#2 @ 113,42: 4x4 | |
#3 @ 112,44: 3x5 | |
""" | |
|> String.splitter("\n", trim: true) | |
assert 14 == SpoilsOfFabric.count_overlaps(example) | |
end | |
end | |
describe "part#2 finding sacred fabric" do | |
test "with example input" do | |
example = | |
""" | |
#1 @ 1,3: 4x4 | |
#2 @ 3,1: 4x4 | |
#3 @ 5,5: 2x2 | |
""" | |
|> String.splitter("\n", trim: true) | |
assert "3" == SpoilsOfFabric.detect_sacred_fabric(example) | |
end | |
test "with a more complex example input" do | |
example = | |
""" | |
#1 @ 111,41: 5x5 | |
#2 @ 115,46: 3x3 | |
#3 @ 113,42: 4x4 | |
#4 @ 112,44: 3x5 | |
""" | |
|> String.splitter("\n", trim: true) | |
assert "2" == SpoilsOfFabric.detect_sacred_fabric(example) | |
end | |
end | |
end | |
end | |
case System.argv() do | |
["--test"] -> | |
ExUnit.start() | |
test.() | |
_ -> | |
overlaps = | |
File.stream!("./3_spoils_of_fabric_input.txt") | |
|> SpoilsOfFabric.count_overlaps() | |
IO.puts("Count of overlaps is... #{overlaps}") | |
id = | |
File.stream!("./3_spoils_of_fabric_input.txt") | |
|> SpoilsOfFabric.detect_sacred_fabric() | |
IO.puts("Sacred fabric id is... #{id}") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ReposeRecord do | |
def calc_part1(logs_str) do | |
logs_str | |
|> String.split("\n", trim: true) | |
|> sort_logs() | |
|> Enum.map(&parse_event/1) | |
|> chart_event_timeline() | |
|> gather_guard_stats() | |
|> calc_weakest_guards_most_slept_metric() | |
end | |
def calc_part2(logs_str) do | |
logs_str | |
|> String.split("\n", trim: true) | |
|> sort_logs() | |
|> Enum.map(&parse_event/1) | |
|> chart_event_timeline() | |
|> gather_guard_stats() | |
|> calc_weakest_minutes_most_slept_metric() | |
end | |
def sort_logs(logs \\ []), do: Enum.sort(logs) | |
def parse_event( | |
<<_::binary-size(12), _hh::binary-size(2), _::binary-size(1), mm::binary-size(2), | |
_::binary-size(2)>> <> "Guard #" <> id_and_action | |
) do | |
[id, action] = String.split(id_and_action, " ", parts: 2) | |
{String.to_integer(mm), String.to_integer(id), action} | |
end | |
def parse_event( | |
<<_::binary-size(12), _hh::binary-size(2), _::binary-size(1), mm::binary-size(2), | |
_::binary-size(2), action::binary>> | |
) do | |
{String.to_integer(mm), action} | |
end | |
def chart_event_timeline(events) do | |
{_, timeline} = Enum.reduce(events, {nil, []}, &map_event_to_timeline/2) | |
Enum.reverse(timeline) | |
end | |
def gather_guard_stats(timeline) do | |
Enum.reduce(timeline, %{}, &update_guard_stat/2) | |
end | |
def calc_weakest_guards_most_slept_metric(stats) do | |
stats | |
|> Enum.max_by(fn {_, {total, _}} -> total end) | |
|> calc_metric_for_guard() | |
end | |
def calc_weakest_minutes_most_slept_metric(stats) do | |
stats | |
|> Enum.max_by(&weakest_minute/1) | |
|> calc_metric_for_guard() | |
end | |
defp weakest_minute({_, {_, minutes}}) do | |
{_, count} = Enum.max_by(minutes, &elem(&1, 1)) | |
count | |
end | |
defp calc_metric_for_guard({id, {_, minutes}}) do | |
{max_minute, _} = Enum.max_by(minutes, &elem(&1, 1)) | |
id * max_minute | |
end | |
defp update_guard_stat({id, sleep_start..sleep_end}, stats) do | |
diff = sleep_end - sleep_start + 1 | |
case stats do | |
%{^id => {total, minutes}} -> | |
minutes = Enum.reduce(sleep_start..sleep_end, minutes, &update_minutes/2) | |
%{stats | id => {total + diff, minutes}} | |
stats -> | |
minutes = Enum.reduce(sleep_start..sleep_end, %{}, &Map.put(&2, &1, 1)) | |
Map.put(stats, id, {diff, minutes}) | |
end | |
end | |
defp update_minutes(minute, minutes) do | |
Map.update(minutes, minute, 1, &(&1 + 1)) | |
end | |
defp map_event_to_timeline({mm, id, "begins shift"}, {_, timeline}) do | |
{{mm, id, "begins shift"}, timeline} | |
end | |
defp map_event_to_timeline({mm, "falls asleep"}, {{_, id, _}, timeline}) do | |
{{mm, id, "falls asleep"}, timeline} | |
end | |
defp map_event_to_timeline({mm, "wakes up"}, {{sleep_mm, id, "falls asleep"}, timeline}) do | |
{{mm, id, "wakes up"}, [{id, sleep_mm..(mm - 1)} | timeline]} | |
end | |
end | |
test = fn -> | |
defmodule ReposeRecordTest do | |
use ExUnit.Case, async: true | |
test "sorts logs" do | |
example = [ | |
"[1518-11-04 00:36] falls asleep", | |
"[1518-11-03 00:29] wakes up", | |
"[1518-11-03 00:05] Guard #10 begins shift", | |
"[1518-11-04 00:46] wakes up" | |
] | |
assert ReposeRecord.sort_logs(example) == [ | |
"[1518-11-03 00:05] Guard #10 begins shift", | |
"[1518-11-03 00:29] wakes up", | |
"[1518-11-04 00:36] falls asleep", | |
"[1518-11-04 00:46] wakes up" | |
] | |
end | |
test "parses a log line" do | |
example = "[1518-11-04 00:36] falls asleep" | |
assert {36, "falls asleep"} == ReposeRecord.parse_event(example) | |
example = "[1518-11-03 00:05] Guard #10 begins shift" | |
assert {5, 10, "begins shift"} == ReposeRecord.parse_event(example) | |
end | |
test "charts event timeline" do | |
example = [ | |
{5, 10, "begins shift"}, | |
{10, "falls asleep"}, | |
{15, "wakes up"}, | |
{20, "falls asleep"}, | |
{30, "wakes up"}, | |
{40, 11, "begins shift"}, | |
{42, "falls asleep"}, | |
{45, "wakes up"} | |
] | |
assert ReposeRecord.chart_event_timeline(example) == [ | |
{10, 10..14}, | |
{10, 20..29}, | |
{11, 42..44} | |
] | |
end | |
test "gathers guard stats" do | |
example = [ | |
{10, 16..20}, | |
{10, 20..24}, | |
{11, 42..44} | |
] | |
assert ReposeRecord.gather_guard_stats(example) == %{ | |
10 => | |
{10, | |
%{ | |
16 => 1, | |
17 => 1, | |
18 => 1, | |
19 => 1, | |
20 => 2, | |
21 => 1, | |
22 => 1, | |
23 => 1, | |
24 => 1 | |
}}, | |
11 => {3, %{42 => 1, 43 => 1, 44 => 1}} | |
} | |
end | |
test "calculates weakest guard's most slept metric" do | |
example = %{ | |
10 => | |
{10, | |
%{ | |
16 => 1, | |
17 => 1, | |
18 => 1, | |
19 => 1, | |
20 => 2, | |
21 => 1, | |
22 => 1, | |
23 => 1, | |
24 => 1 | |
}}, | |
11 => {3, %{42 => 1, 43 => 1, 44 => 1}} | |
} | |
assert 200 == ReposeRecord.calc_weakest_guards_most_slept_metric(example) | |
end | |
test "calculates weakest minute's most slept metric" do | |
example = %{ | |
10 => | |
{10, | |
%{ | |
16 => 1, | |
17 => 1, | |
18 => 1, | |
19 => 1, | |
20 => 2, | |
21 => 1, | |
22 => 1, | |
23 => 1, | |
24 => 1 | |
}}, | |
11 => {7, %{42 => 1, 43 => 5, 44 => 1}} | |
} | |
assert 473 == ReposeRecord.calc_weakest_minutes_most_slept_metric(example) | |
end | |
test "calculates part#1 result" do | |
example = """ | |
[1518-11-01 00:00] Guard #10 begins shift | |
[1518-11-01 00:05] falls asleep | |
[1518-11-01 00:25] wakes up | |
[1518-11-01 00:30] falls asleep | |
[1518-11-01 00:55] wakes up | |
[1518-11-01 23:58] Guard #99 begins shift | |
[1518-11-02 00:40] falls asleep | |
[1518-11-02 00:50] wakes up | |
[1518-11-03 00:05] Guard #10 begins shift | |
[1518-11-03 00:24] falls asleep | |
[1518-11-03 00:29] wakes up | |
[1518-11-04 00:02] Guard #99 begins shift | |
[1518-11-04 00:36] falls asleep | |
[1518-11-04 00:46] wakes up | |
[1518-11-05 00:03] Guard #99 begins shift | |
[1518-11-05 00:45] falls asleep | |
[1518-11-05 00:55] wakes up | |
""" | |
assert 240 == ReposeRecord.calc_part1(example) | |
end | |
test "calculates part#2 result" do | |
example = """ | |
[1518-11-01 00:00] Guard #10 begins shift | |
[1518-11-01 00:05] falls asleep | |
[1518-11-01 00:25] wakes up | |
[1518-11-01 00:30] falls asleep | |
[1518-11-01 00:55] wakes up | |
[1518-11-01 23:58] Guard #99 begins shift | |
[1518-11-02 00:40] falls asleep | |
[1518-11-02 00:50] wakes up | |
[1518-11-03 00:05] Guard #10 begins shift | |
[1518-11-03 00:24] falls asleep | |
[1518-11-03 00:29] wakes up | |
[1518-11-04 00:02] Guard #99 begins shift | |
[1518-11-04 00:36] falls asleep | |
[1518-11-04 00:46] wakes up | |
[1518-11-05 00:03] Guard #99 begins shift | |
[1518-11-05 00:45] falls asleep | |
[1518-11-05 00:55] wakes up | |
""" | |
assert 4455 == ReposeRecord.calc_part2(example) | |
end | |
end | |
end | |
case System.argv() do | |
["--test"] -> | |
ExUnit.start() | |
test.() | |
_ -> | |
result1 = | |
File.read!("./4_repose_record_input.txt") | |
|> ReposeRecord.calc_part1() | |
IO.puts("Strategy #1: weakest guard... -> #{result1}") | |
result2 = | |
File.read!("./4_repose_record_input.txt") | |
|> ReposeRecord.calc_part2() | |
IO.puts("Strategy #2: weakest minute... -> #{result2}") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule AlchemicalReduction do | |
@doc """ | |
For example, again using the polymer dabAcCaCBAcCcaDA from above: | |
* Removing all A/a units produces dbcCCBcCcD. Fully reacting this polymer produces dbCBcD, which has length 6. | |
* Removing all B/b units produces daAcCaCAcCcaDA. Fully reacting this polymer produces daCAcaDA, which has length 8. | |
* Removing all C/c units produces dabAaBAaDA. Fully reacting this polymer produces daDA, which has length 4. | |
* Removing all D/d units produces abAcCaCBAcCcaA. Fully reacting this polymer produces abCBAc, which has length 6. | |
In this example, removing all C/c units was best, producing the answer 4. | |
""" | |
def find_shortest_polymer_alteration_count(polymer_sequence) do | |
?a..?z | |
|> Enum.reduce(:infinity, &dig_minimum_count(&1, &2, polymer_sequence)) | |
end | |
defp dig_minimum_count(char, prev_count, polymer_sequence) do | |
count = | |
polymer_sequence | |
|> String.replace(~r/#{<<char>>}/i, "") | |
|> scan() | |
|> String.length() | |
if count < prev_count, do: count, else: prev_count | |
end | |
@doc """ | |
dabA_cC_aCBAcCcaDA The first 'cC' is removed. | |
dab_Aa_CBAcCcaDA This creates 'Aa', which is removed. | |
dabCBA_cC_caDA Either 'cC' or 'Cc' are removed (the result is the same). | |
dabCBAcaDA No further actions can be taken. | |
""" | |
def scan(polymer_sequence), do: do_scan(polymer_sequence, "") | |
defp do_scan( | |
<<first::binary-size(1), second::binary-size(1), rest::binary>>, | |
<<prev::binary-size(1), processed::binary>> | |
) do | |
cond do | |
should_remove?(first, second) -> | |
do_scan(rest, prev <> processed) | |
should_remove?(prev, first) -> | |
do_scan(second <> rest, processed) | |
true -> | |
do_scan(second <> rest, first <> prev <> processed) | |
end | |
end | |
defp do_scan(<<first::binary-size(1)>>, <<prev::binary-size(1), processed::binary>>) do | |
case should_remove?(prev, first) do | |
true -> String.reverse(processed) | |
false -> String.reverse(first <> prev <> processed) | |
end | |
end | |
# defp do_scan(<<first::binary-size(1)>>, prev), do: do_scan(processed) | |
defp do_scan(<<first::binary-size(1), rest::binary>>, ""), do: do_scan(rest, first) | |
defp do_scan("", processed), do: String.reverse(processed) | |
def should_remove?(char1, char2) do | |
upper_char1 = String.upcase(char1) | |
upper_char2 = String.upcase(char2) | |
cond do | |
char1 != upper_char1 && upper_char1 == char2 -> true | |
char2 != upper_char2 && char1 == upper_char2 -> true | |
true -> false | |
end | |
end | |
end | |
test = fn -> | |
defmodule AlchemicalReductionTest do | |
use ExUnit.Case, async: true | |
test "checks and destroys adjacent characters" do | |
assert true == AlchemicalReduction.should_remove?("a", "A") | |
assert true == AlchemicalReduction.should_remove?("A", "a") | |
assert false == AlchemicalReduction.should_remove?("a", "a") | |
assert false == AlchemicalReduction.should_remove?("A", "A") | |
assert false == AlchemicalReduction.should_remove?("a", "b") | |
assert false == AlchemicalReduction.should_remove?("B", "a") | |
end | |
test "scans polymer sequence" do | |
example = | |
""" | |
dabAcCaCBAcCcaDA | |
""" | |
|> String.trim("\n") | |
assert "dabCBAcaDA" == AlchemicalReduction.scan(example) | |
end | |
test "finds shortest polymer alteration" do | |
example = | |
""" | |
dabAcCaCBAcCcaDA | |
""" | |
|> String.trim("\n") | |
assert 4 == AlchemicalReduction.find_shortest_polymer_alteration_count(example) | |
end | |
end | |
end | |
case System.argv() do | |
["--test"] -> | |
ExUnit.start() | |
test.() | |
_ -> | |
result1 = | |
File.read!("./5_alchemical_reduction_input.txt") | |
|> String.trim("\n") | |
|> AlchemicalReduction.scan() | |
|> String.length() | |
IO.puts("Scanned polymer sequence is... #{result1}") | |
result2 = | |
File.read!("./5_alchemical_reduction_input.txt") | |
|> String.trim("\n") | |
|> AlchemicalReduction.find_shortest_polymer_alteration_count() | |
IO.puts("Shortest polyme alteration count is... #{result2}") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ChronalCoordinates do | |
def calc_part2(str_coords, threshold) do | |
coords = parse_coords(str_coords) | |
{bound_x, bound_y} = find_boundaries(coords) | |
reduce_grid({bound_x, bound_y}, 0, fn x, y, acc -> | |
sum = Enum.reduce(coords, 0, &sum_of_distances(&1, &2, {x, y})) | |
if sum < threshold, do: acc + 1, else: acc | |
end) | |
end | |
def calc_part1(str_coords) do | |
coords = parse_coords(str_coords) | |
boundaries = find_boundaries(coords) | |
grid = populate_grid(coords, boundaries) | |
infinites = find_infinites(grid, boundaries) | |
Enum.reduce(grid, %{}, fn {id, _x, _y}, acc -> | |
cond do | |
id == 0 -> acc | |
MapSet.member?(infinites, id) -> acc | |
true -> Map.update(acc, id, 1, &(&1 + 1)) | |
end | |
end) | |
|> Enum.max_by(&elem(&1, 1)) | |
|> elem(1) | |
end | |
@doc """ | |
Calculates manhattan distance of two points | |
iex> manhattan_distance({1, 1}, {1, 5}) | |
4 | |
iex> manhattan_distance({1, 5}, {10, 8}) | |
12 | |
""" | |
def manhattan_distance({x1, y1}, {x2, y2}) do | |
abs(x1 - x2) + abs(y1 - y2) | |
end | |
def sum_of_distances({_, coord_x, coord_y}, acc, {x, y}) do | |
acc + manhattan_distance({x, y}, {coord_x, coord_y}) | |
end | |
def parse_coords(str_coords) do | |
Enum.reduce(str_coords, {1, []}, fn str, {id, acc} -> | |
[x, y] = String.split(str, ", ") | |
{id + 1, [{id, String.to_integer(x), String.to_integer(y)} | acc]} | |
end) | |
|> elem(1) | |
|> Enum.reverse() | |
end | |
def find_boundaries(coords \\ []) do | |
Enum.reduce(coords, {0, 0}, fn {_id, x, y}, {max_x, max_y} -> | |
{max(x + 1, max_x), max(y + 1, max_y)} | |
end) | |
end | |
def populate_grid(points, {bound_x, bound_y}) do | |
reduce_grid({bound_x, bound_y}, [], fn x, y, acc -> | |
{id, _min_distance, _count} = | |
Enum.reduce(points, {-1, :infinity, 0}, fn {id, px, py}, {old_id, min_distance, count} -> | |
distance = manhattan_distance({x, y}, {px, py}) | |
cond do | |
distance < min_distance -> {id, distance, 1} | |
distance == min_distance -> {0, min_distance, count + 1} | |
true -> {old_id, min_distance, count} | |
end | |
end) | |
[{id, x, y} | acc] | |
end) | |
|> Enum.reverse() | |
end | |
def find_infinites(coords, boundaries) do | |
Enum.reduce(coords, MapSet.new(), &reduce_infinite(&1, &2, boundaries)) | |
end | |
defp reduce_infinite({id, x, y}, acc, {bound_x, bound_y}) | |
when (x == bound_x or y == bound_y or x == 0 or y == 0) and id != 0 do | |
MapSet.put(acc, id) | |
end | |
defp reduce_infinite(_, acc, _), do: acc | |
defp reduce_grid({bound_x, bound_y}, acc, fun) do | |
Enum.reduce(0..bound_y, acc, fn y, acc -> | |
Enum.reduce(0..bound_x, acc, fn x, acc -> | |
fun.(x, y, acc) | |
end) | |
end) | |
end | |
end | |
test = fn -> | |
defmodule ChronalCoordinatesTest do | |
use ExUnit.Case, async: true | |
import ChronalCoordinates | |
test "calculates manhattan distance" do | |
assert 4 == manhattan_distance({1, 1}, {1, 5}) | |
assert 12 == manhattan_distance({1, 5}, {10, 8}) | |
end | |
test "parses coordinates" do | |
assert [{1, 2, 2}, {2, 3, 4}] == parse_coords(["2, 2", "3, 4"]) | |
end | |
test "finds boundaries" do | |
example = [ | |
{1, 1, 1}, | |
{2, 1, 6}, | |
{3, 8, 3}, | |
{4, 3, 4}, | |
{5, 5, 5}, | |
{6, 8, 9} | |
] | |
assert {9, 10} == find_boundaries(example) | |
end | |
test "populates grid" do | |
example = [ | |
{1, 1, 1}, | |
{2, 1, 6}, | |
{3, 8, 3}, | |
{4, 3, 4}, | |
{5, 5, 5}, | |
{6, 8, 9} | |
] | |
result = [ | |
{1, 0, 0}, | |
{1, 1, 0}, | |
{1, 2, 0}, | |
{1, 3, 0}, | |
{1, 4, 0}, | |
{0, 5, 0}, | |
{3, 6, 0}, | |
{3, 7, 0}, | |
{3, 8, 0}, | |
{3, 9, 0}, | |
{1, 0, 1}, | |
{1, 1, 1}, | |
{1, 2, 1}, | |
{1, 3, 1}, | |
{1, 4, 1}, | |
{0, 5, 1}, | |
{3, 6, 1}, | |
{3, 7, 1}, | |
{3, 8, 1}, | |
{3, 9, 1}, | |
{1, 0, 2}, | |
{1, 1, 2}, | |
{1, 2, 2}, | |
{4, 3, 2}, | |
{4, 4, 2}, | |
{5, 5, 2}, | |
{3, 6, 2}, | |
{3, 7, 2}, | |
{3, 8, 2}, | |
{3, 9, 2}, | |
{1, 0, 3}, | |
{1, 1, 3}, | |
{4, 2, 3}, | |
{4, 3, 3}, | |
{4, 4, 3}, | |
{5, 5, 3}, | |
{3, 6, 3}, | |
{3, 7, 3}, | |
{3, 8, 3}, | |
{3, 9, 3}, | |
{0, 0, 4}, | |
{0, 1, 4}, | |
{4, 2, 4}, | |
{4, 3, 4}, | |
{4, 4, 4}, | |
{5, 5, 4}, | |
{5, 6, 4}, | |
{3, 7, 4}, | |
{3, 8, 4}, | |
{3, 9, 4}, | |
{2, 0, 5}, | |
{2, 1, 5}, | |
{0, 2, 5}, | |
{4, 3, 5}, | |
{5, 4, 5}, | |
{5, 5, 5}, | |
{5, 6, 5}, | |
{5, 7, 5}, | |
{3, 8, 5}, | |
{3, 9, 5}, | |
{2, 0, 6}, | |
{2, 1, 6}, | |
{2, 2, 6}, | |
{0, 3, 6}, | |
{5, 4, 6}, | |
{5, 5, 6}, | |
{5, 6, 6}, | |
{5, 7, 6}, | |
{0, 8, 6}, | |
{0, 9, 6}, | |
{2, 0, 7}, | |
{2, 1, 7}, | |
{2, 2, 7}, | |
{0, 3, 7}, | |
{5, 4, 7}, | |
{5, 5, 7}, | |
{5, 6, 7}, | |
{6, 7, 7}, | |
{6, 8, 7}, | |
{6, 9, 7}, | |
{2, 0, 8}, | |
{2, 1, 8}, | |
{2, 2, 8}, | |
{0, 3, 8}, | |
{5, 4, 8}, | |
{5, 5, 8}, | |
{6, 6, 8}, | |
{6, 7, 8}, | |
{6, 8, 8}, | |
{6, 9, 8}, | |
{2, 0, 9}, | |
{2, 1, 9}, | |
{2, 2, 9}, | |
{0, 3, 9}, | |
{6, 4, 9}, | |
{6, 5, 9}, | |
{6, 6, 9}, | |
{6, 7, 9}, | |
{6, 8, 9}, | |
{6, 9, 9} | |
] | |
boundries = {9, 9} | |
assert result == populate_grid(example, boundries) | |
end | |
test "find infinites" do | |
example = [ | |
{1, 0, 0}, | |
{1, 1, 0}, | |
{1, 2, 0}, | |
{1, 3, 0}, | |
{1, 4, 0}, | |
{0, 5, 0}, | |
{3, 6, 0}, | |
{3, 7, 0}, | |
{3, 8, 0}, | |
{3, 9, 0}, | |
{1, 0, 1}, | |
{1, 1, 1}, | |
{1, 2, 1}, | |
{1, 3, 1}, | |
{1, 4, 1}, | |
{0, 5, 1}, | |
{3, 6, 1}, | |
{3, 7, 1}, | |
{3, 8, 1}, | |
{3, 9, 1}, | |
{1, 0, 2}, | |
{1, 1, 2}, | |
{1, 2, 2}, | |
{4, 3, 2}, | |
{4, 4, 2}, | |
{5, 5, 2}, | |
{3, 6, 2}, | |
{3, 7, 2}, | |
{3, 8, 2}, | |
{3, 9, 2}, | |
{1, 0, 3}, | |
{1, 1, 3}, | |
{4, 2, 3}, | |
{4, 3, 3}, | |
{4, 4, 3}, | |
{5, 5, 3}, | |
{3, 6, 3}, | |
{3, 7, 3}, | |
{3, 8, 3}, | |
{3, 9, 3}, | |
{0, 0, 4}, | |
{0, 1, 4}, | |
{4, 2, 4}, | |
{4, 3, 4}, | |
{4, 4, 4}, | |
{5, 5, 4}, | |
{5, 6, 4}, | |
{3, 7, 4}, | |
{3, 8, 4}, | |
{3, 9, 4}, | |
{2, 0, 5}, | |
{2, 1, 5}, | |
{0, 2, 5}, | |
{4, 3, 5}, | |
{5, 4, 5}, | |
{5, 5, 5}, | |
{5, 6, 5}, | |
{5, 7, 5}, | |
{3, 8, 5}, | |
{3, 9, 5}, | |
{2, 0, 6}, | |
{2, 1, 6}, | |
{2, 2, 6}, | |
{0, 3, 6}, | |
{5, 4, 6}, | |
{5, 5, 6}, | |
{5, 6, 6}, | |
{5, 7, 6}, | |
{0, 8, 6}, | |
{0, 9, 6}, | |
{2, 0, 7}, | |
{2, 1, 7}, | |
{2, 2, 7}, | |
{0, 3, 7}, | |
{5, 4, 7}, | |
{5, 5, 7}, | |
{5, 6, 7}, | |
{6, 7, 7}, | |
{6, 8, 7}, | |
{6, 9, 7}, | |
{2, 0, 8}, | |
{2, 1, 8}, | |
{2, 2, 8}, | |
{0, 3, 8}, | |
{5, 4, 8}, | |
{5, 5, 8}, | |
{6, 6, 8}, | |
{6, 7, 8}, | |
{6, 8, 8}, | |
{6, 9, 8}, | |
{2, 0, 9}, | |
{2, 1, 9}, | |
{2, 2, 9}, | |
{0, 3, 9}, | |
{6, 4, 9}, | |
{6, 5, 9}, | |
{6, 6, 9}, | |
{6, 7, 9}, | |
{6, 8, 9}, | |
{6, 9, 9} | |
] | |
boundries = {9, 9} | |
result = MapSet.new([1, 2, 3, 6]) | |
assert result == find_infinites(example, boundries) | |
end | |
test "solves part#1" do | |
example = | |
""" | |
1, 1 | |
1, 6 | |
8, 3 | |
3, 4 | |
5, 5 | |
8, 9 | |
""" | |
|> String.split("\n", trim: true) | |
assert 17 == calc_part1(example) | |
end | |
test "solves part#2" do | |
example = | |
""" | |
1, 1 | |
1, 6 | |
8, 3 | |
3, 4 | |
5, 5 | |
8, 9 | |
""" | |
|> String.split("\n", trim: true) | |
assert 16 == calc_part2(example, 32) | |
end | |
end | |
end | |
case System.argv() do | |
["--test"] -> | |
ExUnit.start() | |
test.() | |
_ -> | |
# File.read!("./furkan_input.txt") | |
result1 = | |
File.read!("./6_chronal_coordinates_input.txt") | |
|> String.split("\n", trim: true) | |
|> ChronalCoordinates.calc_part1() | |
IO.puts("Result of part#1 is... #{result1}") | |
result2 = | |
File.read!("./6_chronal_coordinates_input.txt") | |
|> String.split("\n", trim: true) | |
|> ChronalCoordinates.calc_part2(10_000) | |
IO.puts("Result of part#2 is... #{result2}") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment