Skip to content

Instantly share code, notes, and snippets.

@gungorkocak
Created October 24, 2019 12:43
Show Gist options
  • Save gungorkocak/656d8308769a7d409bbb6677243ae782 to your computer and use it in GitHub Desktop.
Save gungorkocak/656d8308769a7d409bbb6677243ae782 to your computer and use it in GitHub Desktop.
Various Days from Advent of Code Challenge 2018 / with Elixir
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
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
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
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
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
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
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
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