AoC 2023
Mix.install([
{:kino, "~> 0.11.3"}
])
Day 1
input =
Kino.FS.file_path("day_1_input.txt")
|> File.read!()
|> String.split("\n")
defmodule Day1 do
def run(input) do
part_1 =
input
|> Enum.map(&leave_only_numbers/1)
|> Enum.map(&format_numbers/1)
|> Enum.sum()
part_2 =
input
|> Enum.map(&written_number_to_number_string/1)
|> Enum.map(&leave_only_numbers/1)
|> Enum.map(&format_numbers/1)
|> Enum.sum()
{part_1, part_2}
end
defp written_number_to_number_string(string) do
string
|> String.replace("one", "o1e")
|> String.replace("two", "t2o")
|> String.replace("three", "t3e")
|> String.replace("four", "f4r")
|> String.replace("five", "f5e")
|> String.replace("six", "s6x")
|> String.replace("seven", "s7n")
|> String.replace("eight", "e8t")
|> String.replace("nine", "n9e")
end
defp leave_only_numbers(string) do
String.replace(string, ~r/[^0-9]/, "")
end
defp format_numbers(""), do: 0
defp format_numbers(string) do
(String.at(string, 0) <> String.at(string, String.length(string) - 1))
|> Integer.parse()
|> elem(0)
end
end
Day1.run(input)
Day 2
constrains = %{
red: 12,
green: 13,
blue: 14
}
input =
Kino.FS.file_path("day_2_input.txt")
|> File.read!()
|> String.split("\n")
defmodule Day2 do
def run(input, constrains) do
part_1 =
input
|> Enum.reduce(%{}, &get_game_info/2)
|> Enum.filter(&is_game_possible?(&1, constrains))
|> Enum.map(&elem(&1, 0))
|> Enum.sum()
part_2 =
input
|> Enum.reduce(%{}, &get_game_info/2)
|> Enum.map(
&(elem(&1, 1)
|> calculate_fewest_to_make_possible()
|> Map.values()
|> Enum.product())
)
|> Enum.sum()
{part_1, part_2}
end
defp calculate_fewest_to_make_possible(game_info) do
Enum.reduce(game_info, %{red: 0, green: 0, blue: 0}, fn play, acc ->
%{
red: Enum.max([Map.get(play, :red) || 0, Map.get(acc, :red)]),
green: Enum.max([Map.get(play, :green) || 0, Map.get(acc, :green)]),
blue: Enum.max([Map.get(play, :blue) || 0, Map.get(acc, :blue)])
}
end)
end
defp is_game_possible?(game, constrains) do
game
|> elem(1)
|> Enum.map(fn play ->
(Map.get(play, :red) || 0) <= (Map.get(constrains, :red) || 0) &&
(Map.get(play, :green) || 0) <= (Map.get(constrains, :green) || 0) &&
(Map.get(play, :blue) || 0) <= (Map.get(constrains, :blue) || 0)
end)
|> Enum.all?()
end
# "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green"
defp get_game_info(game_string, recursive_map) do
[game_hd | game_tl] = String.split(game_string, ": ")
game_tl = Enum.at(game_tl, 0)
game_number = game_hd |> String.replace("Game ", "") |> Integer.parse() |> elem(0)
game_info = game_tl |> String.split("; ") |> Enum.map(&get_play_info/1)
Map.put(recursive_map, game_number, game_info)
end
# "3 blue, 4 red"
defp get_play_info(play_string) do
play_string
|> String.split(", ")
|> Enum.reduce(%{}, &accumulate_play_info/2)
end
# "3 blue, %{} -> %{blue: 3}
defp accumulate_play_info(play, acc) do
[number, color] = String.split(play, " ")
Map.put(acc, String.to_atom(color), Integer.parse(number) |> elem(0))
end
end
Day2.run(input, constrains)
Day 3
input =
Kino.FS.file_path("day_3_input.txt")
|> File.read!()
|> String.split("\n")
|> Enum.filter(&(&1 != ""))
defmodule Day3 do
require Logger
def run(input) do
row_range = Enum.to_list(0..(length(input) - 1))
part_1 =
row_range
|> Enum.reduce([], &generate_all_checks(input, &1, &2))
|> Enum.filter(&filter_with_checks(input, &1))
|> Enum.map(&Map.get(&1, :number))
|> Enum.sum()
part_2 =
row_range
|> Enum.reduce([], &generate_all_checks(input, &1, &2))
|> Enum.reduce(%{}, &asterisk_transformation(input, &1, &2))
|> Map.values()
|> Enum.filter(&(length(&1) === 2))
|> Enum.map(&Enum.product/1)
|> Enum.sum()
{part_1, part_2}
end
defp asterisk_transformation(input, check_map, accumulator) do
checks = Map.get(check_map, :check)
number = Map.get(check_map, :number)
checks
|> Enum.reduce(accumulator, fn {row, col}, acc ->
line = Enum.at(input, row)
char = String.at(line, col)
with :error <- Integer.parse(char),
true <- char === "*" do
key = inspect({row, col})
value = Map.get(acc, key, []) ++ [number]
Map.put(acc, key, value)
else
_ -> acc
end
end)
end
defp filter_with_checks(input, check_map) do
Map.get(check_map, :check)
|> Enum.map(fn {row, col} ->
line = Enum.at(input, row)
char = String.at(line, col)
with :error <- Integer.parse(char),
true <- char !== "." do
true
else
_ -> false
end
end)
|> Enum.any?(& &1)
end
defp generate_all_checks(input, row_number, accumulator) do
numbers_col_start_and_size = get_numbers_placement_per_row(input, row_number)
line = Enum.at(input, row_number)
row_checks =
Enum.reduce(numbers_col_start_and_size, [], fn tuple, acc ->
[col_start, size] = [elem(tuple, 0), elem(tuple, 1)]
col_end = col_start + size
max_row = length(input) - 1
max_col = String.length(line) - 1
[
%{
number: String.slice(line, col_start, size) |> String.to_integer(),
check: generate_el_checks(row_number, col_start, col_end, max_row, max_col)
}
| acc
]
end)
row_checks ++ accumulator
end
defp generate_el_checks(row, col_start, col_end, max_row, max_col) do
row_above = Enum.max([0, row - 1])
row_below = Enum.min([max_row, row + 1])
col_before = Enum.max([0, col_start - 1])
col_after = Enum.min([max_col, col_end])
range = Enum.to_list(col_before..col_after)
[row_above, row, row_below]
|> Enum.uniq_by(& &1)
|> Enum.flat_map(fn row_number -> Enum.map(range, fn index -> {row_number, index} end) end)
end
defp get_numbers_placement_per_row(input, row) do
Regex.scan(~r/\d+/, Enum.at(input, row), return: :index)
|> Enum.map(&Enum.at(&1, 0))
end
end
Day3.run(input)
Day 4
input =
Kino.FS.file_path("day_4_input.txt")
|> File.read!()
|> String.split("\n")
|> Enum.filter(&(&1 != ""))
require Logger
defmodule Day4 do
def run(input) do
part_1 =
input
|> Enum.map(fn card ->
[hd, tl] = String.split(card, " | ")
get_winning_numbers(hd)
|> calculate_num_of_matches(get_my_numbers(tl))
|> calculate_score()
end)
|> Enum.sum()
quantities_per_card = input |> Enum.map(fn _ -> 1 end)
part_2 =
input
|> Enum.with_index()
|> Enum.reduce(quantities_per_card, fn {card, card_index}, acc ->
[hd, tl] = String.split(card, " | ")
num_of_matches = calculate_num_of_matches(get_winning_numbers(hd), get_my_numbers(tl))
case num_of_matches do
0 ->
acc
n ->
curr_list = acc
positions_to_increase = Enum.to_list((card_index + 1)..(card_index + n))
amount_to_increase = Enum.at(acc, card_index)
increase_numerical_list_at_positions(
curr_list,
positions_to_increase,
amount_to_increase
)
end
end)
|> Enum.sum()
{part_1, part_2}
end
defp get_winning_numbers(string) do
string
|> String.split(": ")
|> Enum.at(1)
|> String.split(" ")
|> Enum.filter(&(&1 != ""))
|> Enum.map(&String.to_integer/1)
end
defp get_my_numbers(string) do
string
|> String.split(" ")
|> Enum.filter(&(&1 != ""))
|> Enum.map(&String.to_integer/1)
end
defp calculate_num_of_matches(winning_numbers, my_numbers) do
Enum.reduce(my_numbers, 0, fn my_num, acc ->
case Enum.find(winning_numbers, &(&1 === my_num)) do
nil ->
acc
_ ->
acc + 1
end
end)
end
defp calculate_score(num_of_matches) do
case num_of_matches do
0 -> 0
p -> 2 ** (p - 1)
end
end
defp increase_numerical_list_at_positions(list, positions, increment) do
Enum.with_index(list, fn el, idx ->
case Enum.find(positions, &(&1 === idx)) do
nil -> el
_ -> el + increment
end
end)
end
end
Day4.run(input)
Day 5
input =
Kino.FS.file_path("day_5_input.txt")
|> File.read!()
|> String.split("\n")
defmodule Day5 do
def run(input) do
triples = get_triples(input)
part_1 = exec(get_tuples_for_part_1(input), triples)
part_2 = exec(get_tuples(input), triples)
{part_1, part_2}
end
defp exec(tuples, triples) do
triples
|> Enum.reduce(tuples, fn triple, acc ->
acc
|> chop_tuples_from_triples(triple)
|> apply_triples_in_tuples(triple)
end)
|> Enum.sort(fn {minA, _}, {minB, _} -> minA <= minB end)
|> Enum.at(0)
|> elem(0)
end
defp get_tuples_for_part_1(input) do
input
|> Enum.at(0)
|> String.split(": ")
|> Enum.at(1)
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
|> Enum.map(fn el -> {el, el} end)
end
defp get_tuples(input) do
input
|> Enum.at(0)
|> String.split(": ")
|> Enum.at(1)
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(2)
|> Enum.map(fn chunk -> {Enum.at(chunk, 0), Enum.at(chunk, 0) + Enum.at(chunk, 1) - 1} end)
end
defp get_triples(input) do
[_ | maps] =
input
|> Enum.chunk_by(&(&1 === ""))
|> Enum.filter(fn arr -> Enum.all?(arr, fn el -> el !== "" end) end)
maps
|> Enum.map(fn [_ | tl] ->
Enum.map(tl, fn el ->
el
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
end)
|> Enum.map(fn [target, source, length] ->
{source, source + length - 1, target - source}
end)
end)
end
defp chop_tuple_from_triple(tuple, triple) do
{triple_min, triple_max, _} = triple
{tuple_min, tuple_max} = tuple
cond do
tuple_max < triple_min or tuple_min > triple_max ->
[tuple]
triple_min <= tuple_min and tuple_max <= triple_max ->
[tuple]
tuple_min < triple_min and tuple_max <= triple_max ->
[
{tuple_min, triple_min - 1},
{triple_min, tuple_max}
]
triple_min <= tuple_min and triple_max < tuple_max ->
[
{tuple_min, triple_max},
{triple_max + 1, tuple_max}
]
tuple_min < triple_min and triple_max < tuple_max ->
[
{tuple_min, triple_min - 1},
{triple_min, triple_max},
{triple_max + 1, tuple_max}
]
end
end
defp chop_tuples_from_triple(tuples, triple) do
Enum.flat_map(tuples, &chop_tuple_from_triple(&1, triple))
end
defp chop_tuple_from_triples(tuple, triples) do
Enum.reduce(triples, [tuple], &chop_tuples_from_triple(&2, &1))
end
defp chop_tuples_from_triples(tuples, triples) do
Enum.flat_map(tuples, &chop_tuple_from_triples(&1, triples))
end
defp apply_triple_in_tuple(tuple, triple) do
{triple_min, triple_max, increment} = triple
{tuple_min, tuple_max} = tuple
cond do
triple_min <= tuple_min and tuple_max <= triple_max ->
new_tuple_min = tuple_min + increment
new_tuple_max = tuple_max + increment
{Enum.min([new_tuple_min, new_tuple_max]), Enum.max([new_tuple_min, new_tuple_max])}
true ->
tuple
end
end
defp apply_triples_in_tuple(tuple, triples) do
new_tuples = Enum.map(triples, &apply_triple_in_tuple(tuple, &1))
contain_new? = Enum.any?(new_tuples, fn t -> t !== tuple end)
case contain_new? do
true -> Enum.filter(new_tuples, fn t -> t !== tuple end)
false -> new_tuples |> Enum.uniq()
end
end
defp apply_triples_in_tuples(tuples, triples) do
Enum.flat_map(tuples, &apply_triples_in_tuple(&1, triples))
end
end
Day5.run(input)
Day 6
input =
Kino.FS.file_path("day_6_input.txt")
|> File.read!()
|> String.split("\n")
defmodule Day6 do
def run(input) do
part_1 = get_list_of_tuples_part_1(input) |> exec()
part_2 = get_list_of_tuples_part_2(input) |> exec()
{part_1, part_2}
end
defp exec(list_of_tuples) do
list_of_tuples
|> Enum.map(fn {max_time, distance_record} ->
Enum.to_list(0..max_time)
|> Enum.map(&calculate_distance(&1, max_time))
|> Enum.count(&(&1 > distance_record))
end)
|> Enum.product()
end
defp get_list_of_tuples_part_1(input) do
[times, distances] =
input
|> Enum.map(fn str ->
str
|> String.replace(~r/\s\s+/, " ")
|> String.split(": ")
|> Enum.at(1)
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
end)
Enum.zip(times, distances)
end
defp get_list_of_tuples_part_2(input) do
[times, distances] =
input
|> Enum.map(fn str ->
str
|> String.replace(~r/\s\s+/, " ")
|> String.split(": ")
|> Enum.at(1)
|> String.replace(" ", "")
|> String.to_integer()
end)
[{times, distances}]
end
defp calculate_distance(time_holding_button, max_time) do
cond do
time_holding_button === 0 -> 0
true -> (max_time - time_holding_button) * time_holding_button
end
end
end
# Day6.run(input)
Day 7
input =
Kino.FS.file_path("day_7_input.txt")
|> File.read!()
|> String.split("\n")
defmodule Cartesian do
def product(characters, n) when is_integer(n) and n > 0 do
Enum.chunk_every(List.flatten(generate_cartesian([], characters, n)), n)
end
defp generate_cartesian(acc, _characters, 0), do: [acc]
defp generate_cartesian(acc, characters, n) do
Enum.reduce(characters, [], fn char, result ->
generate_cartesian([char | acc], characters, n - 1) ++ result
end)
end
end
defmodule CharReplacer do
def exec(string, character, list_of_chars) do
Enum.reduce(list_of_chars, string, fn letter, acc ->
String.replace(acc, ~r/#{character}/, letter, global: false)
end)
end
end
defmodule Day7 do
require Logger
alias Cartesian
@alphabet_1 %{
"A" => 12,
"K" => 11,
"Q" => 10,
"J" => 9,
"T" => 8,
"9" => 7,
"8" => 6,
"7" => 5,
"6" => 4,
"5" => 3,
"4" => 2,
"3" => 1,
"2" => 0
}
@alphabet_2 %{
"A" => 12,
"K" => 11,
"Q" => 10,
"T" => 9,
"9" => 8,
"8" => 7,
"7" => 6,
"6" => 5,
"5" => 4,
"4" => 3,
"3" => 2,
"2" => 1,
"J" => 0
}
@pc1 Cartesian.product(Map.keys(@alphabet_2) |> Enum.filter(&(&1 !== "J")), 1)
@pc2 Cartesian.product(Map.keys(@alphabet_2) |> Enum.filter(&(&1 !== "J")), 2)
@pc3 Cartesian.product(Map.keys(@alphabet_2) |> Enum.filter(&(&1 !== "J")), 3)
@group_order %{
"5" => 6,
"41" => 5,
"32" => 4,
"311" => 3,
"221" => 2,
"2111" => 1,
"11111" => 0
}
def run(input) do
card_bid_list_map = get_card_bid_list_map(input)
part_1 = exec(card_bid_list_map, &group_cards_1/1, @alphabet_1)
part_2 = exec(card_bid_list_map, &group_cards_2/1, @alphabet_2)
{part_1, part_2}
end
defp exec(card_bid_list_map, group_cards_fn, alphabet) do
card_bid_list_map
|> get_cards_from_card_bid_map_list()
|> group_cards_fn.()
|> sort_cards_on_each_group(alphabet)
|> sort_groups()
|> calculate(card_bid_list_map)
end
defp get_card_bid_list_map(input) do
input
|> Enum.map(fn s ->
[card, bid] = String.split(s, " ")
%{card => bid}
end)
end
defp get_cards_from_card_bid_map_list(card_bid_list_map) do
Enum.flat_map(card_bid_list_map, &Map.keys/1)
end
defp get_card_group(card) do
card
|> String.split("")
|> Enum.filter(&(&1 !== ""))
|> Enum.reduce(%{}, fn char, acc ->
case Map.get(acc, char, nil) do
nil -> Map.put(acc, char, 1)
_ -> Map.put(acc, char, Map.get(acc, char) + 1)
end
end)
|> Map.values()
|> Enum.sort(fn a, b -> b < a end)
|> Enum.map(&Integer.to_string/1)
|> Enum.join()
end
defp group_cards_1(cards) do
Enum.reduce(cards, %{}, fn card, acc ->
group = get_card_group(card)
case Map.get(acc, group, nil) do
nil -> Map.put(acc, group, [card])
_ -> Map.put(acc, group, [card | Map.get(acc, group)])
end
end)
end
defp get_card_group_2(card) do
num_of_js =
card
|> String.split("")
|> Enum.filter(&(&1 !== ""))
|> Enum.count(fn s -> s === "J" end)
case num_of_js do
0 ->
get_card_group(card)
5 ->
"5"
4 ->
"5"
n ->
[0, @pc1, @pc2, @pc3]
|> Enum.at(n)
|> Enum.map(fn letters ->
get_card_group(CharReplacer.exec(card, "J", letters))
end)
|> Enum.sort(fn g_a, g_b ->
Map.get(@group_order, g_a, 0) > Map.get(@group_order, g_b, 0)
end)
|> Enum.at(0)
end
end
defp group_cards_2(cards) do
Enum.reduce(cards, %{}, fn card, acc ->
group = get_card_group_2(card)
case Map.get(acc, group, nil) do
nil -> Map.put(acc, group, [card])
_ -> Map.put(acc, group, [card | Map.get(acc, group)])
end
end)
end
defp get_card_sorting_boolean(card_a, card_b, alphabet) do
Enum.reduce_while(0..4, true, fn index, _ ->
char_a = String.at(card_a, index)
char_b = String.at(card_b, index)
char_a_value = Map.get(alphabet, char_a, 0)
char_b_value = Map.get(alphabet, char_b, 0)
# Logger.info("Comparing: #{card_a}, #{char_a}, #{char_a_value} with #{card_b} #{char_b} #{char_b_value}")
cond do
char_a_value > char_b_value -> {:halt, false}
char_a_value < char_b_value -> {:halt, true}
true -> {:cont, true}
end
end)
end
defp get_group_sorting_boolean(group_a_label, group_b_label) do
group_a_value = Map.get(@group_order, group_a_label, 0)
group_b_value = Map.get(@group_order, group_b_label, 0)
group_a_value < group_b_value
end
defp sort_cards_by_group(card_group, alphabet) do
Enum.sort(card_group, &get_card_sorting_boolean(&1, &2, alphabet))
end
defp sort_cards_on_each_group(groups, alphabet) do
Enum.map(groups, fn {label, group} -> %{label => sort_cards_by_group(group, alphabet)} end)
end
defp sort_groups(card_bid_map_sorted_by_cards) do
Enum.sort(card_bid_map_sorted_by_cards, fn g_a, g_b ->
group_a_label = Map.keys(g_a) |> Enum.at(0)
group_b_label = Map.keys(g_b) |> Enum.at(0)
get_group_sorting_boolean(group_a_label, group_b_label)
end)
end
defp calculate(full_sorted, card_bid_list_map) do
card_bid_map =
Enum.reduce(card_bid_list_map, %{}, fn m, acc ->
key = Map.keys(m) |> Enum.at(0)
Map.put(acc, key, Map.get(m, key))
end)
full_sorted
|> Enum.flat_map(fn m -> Map.values(m) end)
|> Enum.flat_map(fn m -> m end)
|> Enum.with_index()
|> Enum.reduce(0, fn {card, index}, acc ->
rank = index + 1
bid = Map.get(card_bid_map, card, 0)
acc + rank * String.to_integer(bid)
end)
end
end
Day7.run(input)
Day 8
input =
Kino.FS.file_path("day_8_input.txt")
|> File.read!()
|> String.split("\n")
|> Enum.filter(&(&1 !== ""))
defmodule BasicMath do
def gcd(a, 0), do: a
def gcd(0, b), do: b
def gcd(a, b), do: gcd(b, rem(a, b))
def lcm(0, 0), do: 0
def lcm(a, b), do: trunc(a * b / gcd(a, b))
end
defmodule Day8 do
require Logger
alias BasicMath
def run(input) do
instructions = get_instructions(input)
map = get_map(input)
part_1 = navigate("AAA", "ZZZ", map, instructions)
part_2 = get_origins(Map.keys(map)) |> get_cycle_size(map, instructions) |> lcm
{part_1, part_2}
end
defp get_instructions(input) do
[instructions | _] = input
instructions
end
defp get_map(input) do
[_ | raw_map] = input
Enum.reduce(raw_map, %{}, fn entry, acc ->
[key, lr] = String.split(entry, " = ")
l = String.split(lr, ", ") |> Enum.at(0) |> String.replace("(", "")
r = String.split(lr, ", ") |> Enum.at(1) |> String.replace(")", "")
case Map.get(acc, key, nil) do
nil -> Map.put(acc, key, {l, r})
_ -> acc
end
end)
end
defp get_origins(origins) do
Enum.filter(origins, &(String.at(&1, 2) === "A"))
end
defp navigate(origin, destiny, map, instructions, steps \\ 0) do
instructions_list = String.split(instructions, "") |> Enum.filter(&(&1 !== ""))
{updated_origin, updated_steps} =
Enum.reduce(instructions_list, {origin, steps}, fn dir, acc ->
idx = if dir === "L", do: 0, else: 1
new_location = Map.get(map, elem(acc, 0)) |> elem(idx)
{new_location, elem(acc, 1) + 1}
end)
case updated_origin do
^destiny -> updated_steps
_ -> navigate(updated_origin, destiny, map, instructions, updated_steps)
end
end
defp navigate_2(origin, map, instructions, steps \\ 0) do
instructions_list = String.split(instructions, "") |> Enum.filter(&(&1 !== ""))
{updated_origin, updated_steps} =
Enum.reduce(instructions_list, {origin, steps}, fn dir, acc ->
idx = if dir === "L", do: 0, else: 1
new_location = Map.get(map, elem(acc, 0)) |> elem(idx)
{new_location, elem(acc, 1) + 1}
end)
if String.at(updated_origin, 2) === "Z",
do: updated_steps,
else: navigate_2(updated_origin, map, instructions, updated_steps)
end
defp get_cycle_size(origins, map, instructions) do
Enum.map(origins, fn origin -> navigate_2(origin, map, instructions) end)
end
defp lcm(list_of_numbers) do
sorted_list_of_numbers = list_of_numbers |> Enum.sort(:asc)
sorted_list_of_numbers
|> Enum.reduce(Enum.at(sorted_list_of_numbers, 0), &BasicMath.lcm(&1, &2))
end
end
Day8.run(input)
Day 9
input =
Kino.FS.file_path("day_9_input.txt")
|> File.read!()
|> String.split("\n")
|> Enum.filter(&(&1 !== ""))
defmodule Day9 do
def run(input) do
part_1 =
input
|> Enum.map(&convert_to_list_of_numbers/1)
|> Enum.map(&convert_to_pyramid(&1, [&1]))
|> Enum.map(&sum_pyramid_last_digits(&1))
|> Enum.sum()
part_2 =
input
|> Enum.map(&convert_to_list_of_numbers/1)
|> Enum.map(&convert_to_pyramid(&1, [&1]))
|> Enum.map(&smart_sum_pyramid_first_digits(&1))
|> Enum.sum()
{part_1, part_2}
end
defp convert_to_list_of_numbers(string) do
String.split(string, " ") |> Enum.map(&String.to_integer/1)
end
defp convert_to_pyramid(list_of_numbers, acc \\ []) do
list_of_differences =
Enum.map(0..(length(list_of_numbers) - 2), fn idx ->
Enum.at(list_of_numbers, idx + 1) - Enum.at(list_of_numbers, idx)
end)
case Enum.all?(list_of_differences, &(&1 === 0)) do
true -> acc
false -> convert_to_pyramid(list_of_differences, [list_of_differences] ++ acc)
end
end
defp sum_pyramid_last_digits(pyramid) do
pyramid
|> Enum.map(&List.last/1)
|> Enum.sum()
end
defp smart_sum_pyramid_first_digits(pyramid) do
Enum.reduce(pyramid, 0, fn list_of_numbers, acc ->
List.first(list_of_numbers) - acc
end)
end
end
Day9.run(input)
Day 10
input =
Kino.FS.file_path("day_10_input.txt")
|> File.read!()
|> String.split("\n")
|> Enum.filter(&(&1 !== ""))
|> Enum.map(fn line -> String.split(line, "") |> Enum.filter(&(&1 !== "")) end)
defmodule Day10 do
require Logger
def run(input) do
map = get_map(input)
start_pos = get_start_pos(input)
[dir_a_results, dir_b_results] =
get_start_dirs(map, start_pos) |> Enum.map(&move(map, start_pos, &1))
final =
Map.keys(dir_b_results)
|> Enum.map(fn key ->
[dir_a_results, dir_b_results] |> Enum.map(&Map.get(&1, key)) |> Enum.min()
end)
part_1 = Enum.max(final)
###
coords = get_coords(map, start_pos, Enum.at(get_start_dirs(map, start_pos), 0))
part_2 = coords |> shoelace() |> pick(coords)
{part_1, part_2}
end
defp get_map(input) do
Enum.reduce(Enum.with_index(input), %{}, fn {row, row_idx}, acc1 ->
Enum.reduce(Enum.with_index(row), acc1, fn {col, col_idx}, acc2 ->
Map.put(acc2, inspect({row_idx, col_idx}), col)
end)
end)
end
defp get_start_pos(input) do
row_idx = Enum.find_index(input, &Enum.member?(&1, "S"))
col_idx = Enum.find_index(Enum.at(input, row_idx), &(&1 === "S"))
{row_idx, col_idx}
end
defp get_start_dirs(map, {s_r, s_c}) do
up = {s_r - 1, s_c}
down = {s_r + 1, s_c}
left = {s_r, s_c - 1}
right = {s_r, s_c + 1}
is_up? = Enum.member?(["|", "F", "7"], Map.get(map, tts(up)))
is_down? = Enum.member?(["|", "J", "L"], Map.get(map, tts(down)))
is_left? = Enum.member?(["-", "F", "L"], Map.get(map, tts(left)))
is_right? = Enum.member?(["-", "J", "7"], Map.get(map, tts(right)))
[
{up, is_up?},
{down, is_down?},
{left, is_left?},
{right, is_right?}
]
|> Enum.filter(&elem(&1, 1))
|> Enum.map(&elem(&1, 0))
end
defp get_coords(map, p, c, coords \\ []) do
case Map.get(map, tts(c)) === "S" do
true -> coords
false -> get_coords(map, c, calculate(map, p, c), coords ++ [c])
end
end
defp move(map, p, c, result_map \\ %{}) do
case Map.get(map, tts(c)) === "S" do
true ->
result_map
false ->
value = Map.get(result_map, tts(p), 0) + 1
result_map = Map.put(result_map, tts(c), value)
move(map, c, calculate(map, p, c), result_map)
end
end
defp shoelace(coords) do
Enum.reduce(Enum.with_index(coords), 0, fn {coord, idx}, acc ->
case idx === length(coords) - 1 do
true -> acc + det(coord, Enum.at(coords, 0))
false -> acc + det(coord, Enum.at(coords, idx + 1))
end
end)
end
defp pick(twoA, coords) do
abs(twoA) / 2 + 1 - (length(coords) + 1) / 2
end
###
defp calculate(map, {p_x, p_y}, {c_x, c_y} = c) do
pipe = Map.get(map, tts(c))
case pipe do
"." -> c
"S" -> c
"-" -> if p_y < c_y, do: {c_x, c_y + 1}, else: {c_x, c_y - 1}
"|" -> if p_x < c_x, do: {c_x + 1, c_y}, else: {c_x - 1, c_y}
"L" -> if p_x < c_x, do: {c_x, c_y + 1}, else: {c_x - 1, c_y}
"F" -> if p_x === c_x, do: {c_x + 1, c_y}, else: {c_x, c_y + 1}
"J" -> if p_y < c_y, do: {c_x - 1, c_y}, else: {c_x, c_y - 1}
"7" -> if p_y < c_y, do: {c_x + 1, c_y}, else: {c_x, c_y - 1}
end
end
# tuple to string
defp tts({x, y}) do
inspect({x, y})
end
# string to tuple
defp stt(str) do
[a, b] = String.split(str, ", ")
{
String.replace(a, "{", "") |> String.to_integer(),
String.replace(b, "}", "") |> String.to_integer()
}
end
def det({p_x, p_y}, {c_x, c_y}) do
{x_1, y_1} = {p_y, p_x}
{x_2, y_2} = {c_y, c_x}
x_1 * y_2 - x_2 * y_1
end
end
Day10.run(input)
Day 11
input =
Kino.FS.file_path("day_11_input.txt")
|> File.read!()
|> String.split("\n")
|> Enum.filter(&(&1 !== ""))
|> Enum.map(fn row -> String.split(row, "") |> Enum.filter(&(&1 !== "")) end)
defmodule Day11 do
def run(input) do
sum_of_all_galaxy_pair_distances =
input
|> get_expanded_universe()
|> get_universe_map()
|> remove_empty_spaces()
|> calculate_min_distances()
|> Map.values()
|> Enum.sum()
part_1 = sum_of_all_galaxy_pair_distances / 2
end
defp get_universe_map(universe) do
Enum.with_index(universe)
|> Enum.reduce(%{}, fn {row, row_idx}, acc_1 ->
Enum.reduce(Enum.with_index(row), acc_1, fn {col, col_idx}, acc_2 ->
Map.put(acc_2, {row_idx, col_idx}, col)
end)
end)
end
defp get_universe_dimensions(universe) do
{length(universe) - 1, length(Enum.at(universe, 0)) - 1}
end
defp get_expanded_universe(universe) do
universe
|> expand_universe_on_rows()
|> expand_universe_on_cols()
end
defp expand_universe_on_rows(universe) do
{max_rows, _} = get_universe_dimensions(universe)
row_slices_idx =
Enum.reduce(0..max_rows, [], fn row_idx, acc ->
row = Enum.at(universe, row_idx)
if Enum.all?(row, &(&1 === ".")), do: acc ++ [row_idx], else: acc
end) ++ [max_rows]
{_, expanded_result} =
Enum.reduce(row_slices_idx, {0, []}, fn empty_row_idx, {last_row_idx, universe_slice} ->
{empty_row_idx, universe_slice ++ Enum.slice(universe, last_row_idx..empty_row_idx)}
end)
expanded_result
end
defp expand_universe_on_cols(universe) do
{_, max_cols} = get_universe_dimensions(universe)
col_slices_idx =
Enum.reduce(0..max_cols, [], fn col_idx, acc ->
col = Enum.map(universe, &Enum.at(&1, col_idx))
if Enum.all?(col, &(&1 === ".")), do: acc ++ [col_idx], else: acc
end) ++ [max_cols]
Enum.map(universe, fn row ->
{_, col_expansion} =
Enum.reduce(col_slices_idx, {0, []}, fn empty_col_idx, {last_col_idx, universe_slice} ->
{empty_col_idx, universe_slice ++ Enum.slice(row, last_col_idx..empty_col_idx)}
end)
col_expansion
end)
end
defp remove_empty_spaces(universe_map) do
Enum.reduce(universe_map, %{}, fn {key, value}, acc ->
if value !== ".", do: Map.put(acc, key, value), else: acc
end)
end
defp calculate_min_distances(universe_map) do
galaxies_coordinates = Map.keys(universe_map)
Enum.reduce(universe_map, %{}, fn {key, _}, acc ->
min_distance =
Enum.map(galaxies_coordinates, fn {g_x, g_y} ->
abs(g_x - elem(key, 0)) + abs(g_y - elem(key, 1))
end)
|> Enum.filter(&(&1 > 0))
|> Enum.sum()
Map.put(acc, key, min_distance)
end)
end
end
Day11.run(input)