Advent of Code
Mix.install([
{:req, "~> 0.3.2"}
])
stream_for_day! = fn day ->
unless File.exists?("AdventOfCode/input/#{day}.txt") do
content =
Req.get!("https://adventofcode.com/2022/day/#{day}/input",
headers: [{"cookie", "session=#{System.fetch_env!("LB_SESSION")}"}]
).body
File.write!("AdventOfCode/input/#{day}.txt", content)
end
File.stream!("AdventOfCode/input/#{day}.txt")
end
file_for_day! = fn day ->
stream_for_day!.(day) |> Enum.join("")
end
Day 1
# Part 1
stream_for_day!.(1)
|> Enum.reduce({0, 0}, fn
"\n", {current_max, current} ->
{max(current_max, current), 0}
value, {current_max, current} ->
{current_max, current + (String.trim(value) |> String.to_integer())}
end)
|> then(fn {current_max, current} -> max(current_max, current) end)
# Part 2
stream_for_day!.(1)
|> Enum.reduce({[0, 0, 0], 0}, fn
"\n", {current_max, current} ->
{[current | current_max] |> Enum.sort(:desc) |> Enum.take(3), 0}
value, {current_max, current} ->
{current_max, current + (String.trim(value) |> String.to_integer())}
end)
|> then(fn {current_max, current} ->
[current | current_max] |> Enum.sort(:desc) |> Enum.take(3)
end)
|> Enum.sum()
Day 2
# Part 1
opponent_mapping = %{
"A" => :rock,
"B" => :paper,
"C" => :scissors
}
my_mapping = %{
"X" => :rock,
"Y" => :paper,
"Z" => :scissors
}
points = %{
rock: 1,
paper: 2,
scissors: 3,
draw: 3,
opponent_win: 0,
my_win: 6
}
result = fn
same_move, same_move -> :draw
:rock, :scissors -> :opponent_win
:paper, :rock -> :opponent_win
:scissors, :paper -> :opponent_win
_, _ -> :my_win
end
stream_for_day!.(2)
|> Enum.reduce(0, fn line, acc ->
[opponent_move, my_move] = String.trim(line) |> String.split(" ")
acc + points[result.(opponent_mapping[opponent_move], my_mapping[my_move])] +
points[my_mapping[my_move]]
end)
# Part 2
opponent_mapping = %{
"A" => :rock,
"B" => :paper,
"C" => :scissors
}
my_mapping = %{
"X" => :lose,
"Y" => :draw,
"Z" => :win
}
points = %{
rock: 1,
paper: 2,
scissors: 3,
draw: 3,
lose: 0,
win: 6
}
move_mapping = %{
rock: %{win: :paper, lose: :scissors},
paper: %{win: :scissors, lose: :rock},
scissors: %{win: :rock, lose: :paper}
}
stream_for_day!.(2)
|> Enum.reduce(0, fn line, acc ->
[opponent_move_raw, my_result_raw] = String.trim(line) |> String.split(" ")
my_result = my_mapping[my_result_raw]
opponent_move = opponent_mapping[opponent_move_raw]
acc + points[my_result] + points[move_mapping[opponent_move][my_result] || opponent_move]
end)
Day 3
# Part 1
stream_for_day!.(3)
|> Enum.reduce(0, fn line, acc ->
line = String.trim(line)
{part_1, part_2} = String.split_at(line, div(String.length(line), 2))
part_1 = String.splitter(part_1, "", trim: true)
part_2 = String.splitter(part_2, "", trim: true)
intersection = MapSet.intersection(MapSet.new(part_1), MapSet.new(part_2))
[element] = MapSet.to_list(intersection)
[element] = String.to_charlist(element)
if element >= ?a do
element - ?a + 1
else
element - ?A + 27
end
|> Kernel.+(acc)
end)
# Part 2
stream_for_day!.(3)
|> Stream.map(&String.trim/1)
|> Stream.chunk_every(3)
|> Enum.reduce(0, fn [elf1, elf2, elf3], acc ->
elf1_items = elf1 |> String.splitter("", trim: true) |> MapSet.new()
elf2_items = elf2 |> String.splitter("", trim: true) |> MapSet.new()
elf3_items = elf3 |> String.splitter("", trim: true) |> MapSet.new()
intersection =
MapSet.intersection(MapSet.new(elf1_items), MapSet.new(elf2_items))
|> MapSet.intersection(elf3_items)
[element] = MapSet.to_list(intersection)
[element] = String.to_charlist(element)
if element >= ?a do
element - ?a + 1
else
element - ?A + 27
end
|> Kernel.+(acc)
end)
Day 4
# Part 1
stream_for_day!.(4)
|> Stream.map(&String.trim/1)
|> Enum.count(fn pair ->
[elf1, elf2] = String.split(pair, ",")
[elf1_start, elf1_end] = String.split(elf1, "-") |> Enum.map(&String.to_integer/1)
[elf2_start, elf2_end] = String.split(elf2, "-") |> Enum.map(&String.to_integer/1)
(elf1_start <= elf2_start and elf1_end >= elf2_end) or
(elf2_start <= elf1_start and elf2_end >= elf1_end)
end)
# Part 2
stream_for_day!.(4)
|> Stream.map(&String.trim/1)
|> Enum.count(fn pair ->
[elf1, elf2] = String.split(pair, ",")
[elf1_start, elf1_end] = String.split(elf1, "-") |> Enum.map(&String.to_integer/1)
[elf2_start, elf2_end] = String.split(elf2, "-") |> Enum.map(&String.to_integer/1)
not Range.disjoint?(elf1_start..elf1_end, elf2_start..elf2_end)
end)
Day 5
# Part 1
{tower, moves} =
stream_for_day!.(5)
|> Stream.map(&String.trim(&1, "\n"))
|> Stream.reject(&(&1 == ""))
|> Enum.split_while(&(not String.starts_with?(&1, "move")))
columns =
tower
|> Enum.drop(-1)
|> Enum.reduce(%{}, fn line, acc ->
line
|> String.to_charlist()
|> Enum.chunk_every(4)
|> Enum.map(&to_string/1)
|> IO.inspect()
|> Enum.map(&String.replace(&1, ~r{\[|\]| }, "", global: true))
|> Enum.with_index(1)
|> Enum.reduce(acc, fn
{"", _index}, acc ->
acc
{column, index}, acc ->
Map.update(acc, index, [column], &[column | &1])
end)
end)
moves
|> Enum.map(fn line ->
[_, number_of_crates, _, from, _, to] = line |> String.split(" ")
[number_of_crates, from, to] |> Enum.map(&String.to_integer/1)
end)
|> Enum.reduce(columns, fn [number_of_crates, from, to], columns ->
crates_to_move = Enum.take(columns[from], -number_of_crates)
columns
|> Map.update!(from, fn column ->
column |> Enum.drop(-number_of_crates)
end)
|> Map.update!(to, fn column ->
column ++ Enum.reverse(crates_to_move)
end)
end)
|> Enum.map(fn {_, column} ->
List.last(column)
end)
|> Enum.join()
# Part 2
{tower, moves} =
stream_for_day!.(5)
|> Stream.map(&String.trim(&1, "\n"))
|> Stream.reject(&(&1 == ""))
|> Enum.split_while(&(not String.starts_with?(&1, "move")))
columns =
tower
|> Enum.drop(-1)
|> Enum.reduce(%{}, fn line, acc ->
line
|> String.to_charlist()
|> Enum.chunk_every(4)
|> Enum.map(&to_string/1)
|> Enum.map(&String.replace(&1, ~r{\[|\]| }, "", global: true))
|> Enum.with_index(1)
|> Enum.reduce(acc, fn
{"", _index}, acc ->
acc
{column, index}, acc ->
Map.update(acc, index, [column], &[column | &1])
end)
end)
moves
|> Enum.map(fn line ->
[_, number_of_crates, _, from, _, to] = line |> String.split(" ")
[number_of_crates, from, to] |> Enum.map(&String.to_integer/1)
end)
|> Enum.reduce(columns, fn [number_of_crates, from, to], columns ->
crates_to_move = Enum.take(columns[from], -number_of_crates)
columns
|> Map.update!(from, fn column ->
column |> Enum.drop(-number_of_crates)
end)
|> Map.update!(to, fn column ->
column ++ crates_to_move
end)
end)
|> Enum.map(fn {_, column} ->
List.last(column)
end)
|> Enum.join()
Day 6
# Part 1
check_if_continue = fn list, index ->
if list == Enum.uniq(list) do
{:halt, index}
else
{:cont, list}
end
end
first_n_different_characters_at = fn n ->
stream_for_day!.(6)
|> Enum.fetch!(0)
|> String.split("", trim: true)
|> Enum.with_index(1)
|> Enum.reduce_while([], fn
{elem, _index}, acc when length(acc) < n - 1 ->
acc = acc ++ [elem]
{:cont, acc}
{elem, index}, acc when length(acc) == n - 1 ->
acc = acc ++ [elem]
check_if_continue.(acc, index)
{elem, index}, acc ->
acc = Enum.drop(acc, 1) ++ [elem]
check_if_continue.(acc, index)
end)
end
first_n_different_characters_at.(4)
# Part 2
first_n_different_characters_at.(14)
Day 7
defmodule Day7 do
def generate_tree(input, directory_content \\ %{}) do
case List.pop_at(input, 0) do
{"$ ls", input} ->
{ls_result, input} = Enum.split_while(input, &(not String.starts_with?(&1, "$")))
directory_content =
Map.new(ls_result, fn
"dir " <> dir_name ->
{dir_name, %{type: :dir, size: nil, children: %{}}}
file_line ->
[file_size, file_name] = String.split(file_line, " ")
{file_name, %{type: :file, size: String.to_integer(file_size)}}
end)
generate_tree(input, directory_content)
{"$ cd ..", input} ->
{input, directory_content}
{"$ cd " <> directory_to_change, input} ->
{input, children_content} = Day7.generate_tree(input)
generate_tree(
input,
put_in(directory_content, [directory_to_change, :children], children_content)
)
_ ->
{input, directory_content}
end
end
def calculate_dir_sizes(tree) do
Enum.reduce(tree, {0, []}, fn
{name, %{type: :dir} = dir}, {size, acc} ->
{add_size, add_acc} = calculate_dir_sizes(dir.children)
if add_size <= 100_000 do
{add_size + size, [{name, add_size}] ++ acc ++ add_acc}
else
{add_size + size, acc ++ add_acc}
end
{_name, %{size: add_size}}, {size, acc} ->
{add_size + size, acc}
end)
end
def calculate_all_dir_sizes(tree) do
Enum.reduce(tree, {0, []}, fn
{name, %{type: :dir} = dir}, {size, acc} ->
{add_size, add_acc} = calculate_all_dir_sizes(dir.children)
{add_size + size, [{name, add_size}] ++ acc ++ add_acc}
{_name, %{size: add_size}}, {size, acc} ->
{add_size + size, acc}
end)
end
end
input = stream_for_day!.(7) |> Stream.map(&String.trim/1) |> Enum.to_list()
{"$ cd /", input} = List.pop_at(input, 0)
{_, tree} = Day7.generate_tree(input)
{space_taken, to_sum} = Day7.calculate_dir_sizes(tree)
to_sum |> Enum.map(fn {_name, size} -> size end) |> Enum.sum()
free_space = 70_000_000 - space_taken
need_to_delete = 30_000_000 - free_space
{_, to_sort} = Day7.calculate_all_dir_sizes(tree)
to_sort
|> Enum.sort_by(fn {_name, size} -> size end)
|> Enum.find(fn {_name, size} -> size >= need_to_delete end)
|> elem(1)
Day 8
# Part 1
rows = []
columns = []
{rows, columns} =
stream_for_day!.(8)
|> Stream.map(&String.trim/1)
|> Enum.reduce({rows, columns}, fn line, {rows, columns} ->
row = String.split(line, "", trim: true) |> Enum.map(&String.to_integer/1)
number_of_rows = length(rows)
row_length = length(row)
row = row |> Enum.with_index(number_of_rows * row_length + 1)
columns =
if columns == [] do
row |> Enum.map(&List.wrap/1)
else
Enum.zip_reduce([columns, row], [], fn [column, tree], acc ->
[[tree | column] | acc]
end)
|> Enum.reverse()
end
{[row | rows], columns}
end)
reduce_list = fn {height, id}, {max_height, visible_trees} ->
if height > max_height do
{height, MapSet.put(visible_trees, id)}
else
{max_height, visible_trees}
end
end
set1 =
Enum.reduce(rows, MapSet.new(), fn row, acc ->
set1 = row |> Enum.reduce({-1, MapSet.new()}, reduce_list) |> elem(1)
set2 = row |> Enum.reverse() |> Enum.reduce({-1, MapSet.new()}, reduce_list) |> elem(1)
MapSet.union(set1, set2) |> MapSet.union(acc)
end)
set2 =
Enum.reduce(columns, MapSet.new(), fn column, acc ->
set1 = column |> Enum.reduce({-1, MapSet.new()}, reduce_list) |> elem(1)
set2 = column |> Enum.reverse() |> Enum.reduce({-1, MapSet.new()}, reduce_list) |> elem(1)
MapSet.union(set1, set2) |> MapSet.union(acc)
end)
[List.first(rows), List.last(rows), List.first(columns), List.last(columns)]
|> List.flatten()
|> Enum.map(&elem(&1, 1))
|> MapSet.new()
|> MapSet.union(set1)
|> MapSet.union(set2)
|> MapSet.size()
# Part 2
map =
stream_for_day!.(8)
|> Stream.map(&String.trim/1)
|> Enum.reduce(%{}, fn line, map ->
row =
String.split(line, "", trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.with_index(fn element, index -> {index, element} end)
|> Map.new()
number_of_rows = map_size(map)
Map.put(map, number_of_rows, row)
end)
for x <- 0..98, y <- 0..98 do
house_height = map[x][y]
left =
Enum.reduce_while((x - 1)..0//-1, 0, fn x, acc ->
if map[x][y] < house_height, do: {:cont, acc + 1}, else: {:halt, acc + 1}
end)
right =
Enum.reduce_while((x + 1)..98, 0, fn x, acc ->
if map[x][y] < house_height, do: {:cont, acc + 1}, else: {:halt, acc + 1}
end)
top =
Enum.reduce_while((y - 1)..0//-1, 0, fn y, acc ->
if map[x][y] < house_height, do: {:cont, acc + 1}, else: {:halt, acc + 1}
end)
bottom =
Enum.reduce_while((y + 1)..98, 0, fn y, acc ->
if map[x][y] < house_height, do: {:cont, acc + 1}, else: {:halt, acc + 1}
end)
left * right * top * bottom
end
|> Enum.max()
Day 9
# Part 1
defmodule Day9 do
def step([line | rest_lines], head_position, tail_position, all_tail_positions) do
[direction, steps] = String.split(line, " ")
move_shift = %{
"U" => {0, 1},
"D" => {0, -1},
"R" => {1, 0},
"L" => {-1, 0}
}
{head_position, tail_position, all_tail_positions} =
move(
String.to_integer(steps),
move_shift[direction],
head_position,
tail_position,
all_tail_positions
)
step(rest_lines, head_position, tail_position, all_tail_positions)
end
def step([], _, _, all_tail_positions), do: all_tail_positions
defp move(0, _move_shift, head_position, tail_position, all_tail_positions) do
{head_position, tail_position, all_tail_positions}
end
defp move(steps, move_shift, head_position, tail_position, all_tail_positions) do
head_position = move_head(head_position, move_shift)
tail_position = move_tail(tail_position, head_position)
move(
steps - 1,
move_shift,
head_position,
tail_position,
MapSet.put(all_tail_positions, tail_position)
)
end
defp move_head({head_x_position, head_y_position}, {shift_x, shift_y}),
do: {head_x_position + shift_x, head_y_position + shift_y}
defp move_tail({tail_x, tail_y}, {head_x, tail_y}) when head_x - tail_x >= 2,
do: {head_x - 1, tail_y}
defp move_tail({tail_x, tail_y}, {head_x, tail_y}) when head_x - tail_x <= -2,
do: {head_x + 1, tail_y}
defp move_tail({tail_x, tail_y}, {tail_x, head_y}) when head_y - tail_y >= 2,
do: {tail_x, head_y - 1}
defp move_tail({tail_x, tail_y}, {tail_x, head_y}) when head_y - tail_y <= -2,
do: {tail_x, head_y + 1}
defp move_tail({tail_x, tail_y}, {head_x, head_y})
when abs(tail_x - head_x) > 1 or abs(tail_y - head_y) > 1 do
cond do
tail_y - head_y == 2 -> {head_x, head_y + 1}
tail_y - head_y == -2 -> {head_x, head_y - 1}
tail_x - head_x == 2 -> {head_x + 1, head_y}
tail_x - head_x == -2 -> {head_x - 1, head_y}
end
end
defp move_tail(tail_position, _head_position), do: tail_position
end
stream_for_day!.(9)
|> Stream.map(&String.trim/1)
|> Enum.to_list()
|> Day9.step({0, 0}, {0, 0}, MapSet.new([{0, 0}]))
|> MapSet.size()
# Part 2
defmodule Day9 do
def step([line | rest_lines], positions, all_9_positions) do
[direction, steps] = String.split(line, " ")
move_shift = %{
"U" => {0, 1},
"D" => {0, -1},
"R" => {1, 0},
"L" => {-1, 0}
}
{positions, all_9_positions} =
move(String.to_integer(steps), move_shift[direction], positions, all_9_positions)
step(rest_lines, positions, all_9_positions)
end
def step([], _positions, all_9_positions), do: all_9_positions
defp move(0, _move_shift, positions, all_9_positions) do
{positions, all_9_positions}
end
defp move(steps, move_shift, positions, all_9_positions) do
positions = Map.put(positions, 0, move_head(positions[0], move_shift))
positions =
Enum.reduce(1..9, positions, fn i, positions ->
Map.put(positions, i, move_tail(positions[i], positions[i - 1]))
end)
all_9_positions = MapSet.put(all_9_positions, positions[9])
move(steps - 1, move_shift, positions, all_9_positions)
end
defp move_head({head_x_position, head_y_position}, {shift_x, shift_y}),
do: {head_x_position + shift_x, head_y_position + shift_y}
defp move_tail({tail_x, tail_y}, {head_x, tail_y}) when head_x - tail_x >= 2,
do: {tail_x + 1, tail_y}
defp move_tail({tail_x, tail_y}, {head_x, tail_y}) when head_x - tail_x <= -2,
do: {tail_x - 1, tail_y}
defp move_tail({tail_x, tail_y}, {tail_x, head_y}) when head_y - tail_y >= 2,
do: {tail_x, tail_y + 1}
defp move_tail({tail_x, tail_y}, {tail_x, head_y}) when head_y - tail_y <= -2,
do: {tail_x, tail_y - 1}
defp move_tail({tail_x, tail_y}, {head_x, head_y})
when abs(tail_x - head_x) + abs(tail_y - head_y) >= 3 do
y = sign(head_y - tail_y)
x = sign(head_x - tail_x)
{tail_x + x, tail_y + y}
end
defp move_tail(tail_position, _head_position), do: tail_position
defp sign(number) when number >= 0, do: 1
defp sign(_number), do: -1
end
start_positions =
for i <- 0..9, into: %{} do
{i, {0, 0}}
end
stream_for_day!.(9)
|> Stream.map(&String.trim/1)
|> Enum.to_list()
|> Day9.step(start_positions, MapSet.new([{0, 0}]))
|> MapSet.size()
Day 10
# Part 1
stream_for_day!.(10)
|> Stream.map(&String.trim/1)
|> Enum.reduce({1, 0, 0}, fn instruction, {register_value, cycle, sum} ->
{cycle_start, cycle_end, end_value} =
case instruction do
"noop" ->
{cycle + 1, cycle + 1, register_value}
"addx " <> value ->
{cycle + 1, cycle + 2, register_value + String.to_integer(value)}
end
sum =
[20, 60, 100, 140, 180, 220]
|> Enum.find(fn x ->
x in cycle_start..cycle_end
end)
|> case do
nil ->
sum
x ->
sum + x * register_value
end
{end_value, cycle_end, sum}
end)
# Part 2
{lit_pixels, _, _} =
stream_for_day!.(10)
|> Stream.map(&String.trim/1)
|> Enum.reduce({%{}, 1, 0}, fn instruction, {lit_pixels, register_value, cycle} ->
{cycle_start, cycle_end, end_value} =
case instruction do
"noop" ->
{cycle, cycle + 1, register_value}
"addx " <> value ->
{cycle + 1, cycle + 2, register_value + String.to_integer(value)}
end
lit_pixels =
cycle_start..cycle_end
|> Enum.reduce(lit_pixels, fn cycle, lit_pixels ->
if Integer.mod(cycle - 1, 40) in (register_value - 1)..(register_value + 1) do
Map.put(lit_pixels, cycle, "#")
else
lit_pixels
end
end)
{lit_pixels, end_value, cycle_end}
end)
1..240
|> Enum.map(fn i ->
if Map.has_key?(lit_pixels, i) do
"#"
else
"."
end
end)
|> Enum.chunk_every(40)
|> Enum.map(&Enum.join(&1, ""))
|> Enum.join("\n")
|> IO.puts()
Day 11
# Part 1
monkeys =
file_for_day!.(11)
|> String.split("\n\n")
|> Map.new(fn monkey_input ->
[monkey_name_line, starting_items_line, operration_line, test_line, true_line, false_line] =
monkey_input |> String.split("\n", trim: true) |> Enum.map(&String.trim(&1))
monkey_id =
monkey_name_line
|> String.trim_trailing(":")
|> String.trim_leading("Monkey ")
|> String.to_integer()
starting_items =
starting_items_line
|> String.trim_leading("Starting items: ")
|> String.split(", ")
|> Enum.map(&String.to_integer/1)
operation_string = operration_line |> String.trim_leading("Operation: new = ")
operation = fn old -> Code.eval_string(operation_string, old: old) |> elem(0) end
divisor = test_line |> String.trim_leading("Test: divisible by ") |> String.to_integer()
true_monkey =
true_line |> String.trim_leading("If true: throw to monkey ") |> String.to_integer()
false_monkey =
false_line |> String.trim_leading("If false: throw to monkey ") |> String.to_integer()
{monkey_id,
%{
items: starting_items,
operation: operation,
divisor: divisor,
true_monkey: true_monkey,
false_monkey: false_monkey,
counter: 0
}}
end)
[a, b] =
Enum.reduce(1..20, monkeys, fn _round, monkeys ->
Enum.reduce(0..(map_size(monkeys) - 1), monkeys, fn monkey_id, monkeys ->
Enum.reduce(monkeys[monkey_id].items, monkeys, fn _item, monkeys ->
current_monkey = monkeys[monkey_id]
{current_item, rest_of_items} = List.pop_at(current_monkey.items, 0)
monkeys = put_in(monkeys, [monkey_id, :items], rest_of_items)
monkeys = update_in(monkeys, [monkey_id, :counter], &(&1 + 1))
worry_level = floor(current_monkey.operation.(current_item) / 3)
pass_to_monkey_id =
if Integer.mod(worry_level, current_monkey.divisor) == 0 do
current_monkey.true_monkey
else
current_monkey.false_monkey
end
update_in(monkeys, [pass_to_monkey_id, :items], &(&1 ++ [worry_level]))
end)
end)
end)
|> Enum.map(&elem(&1, 1).counter)
|> Enum.sort(:desc)
|> Enum.take(2)
a * b
# Part 2
test_input = """
Monkey 0:
Starting items: 79, 98
Operation: new = old * 19
Test: divisible by 23
If true: throw to monkey 2
If false: throw to monkey 3
Monkey 1:
Starting items: 54, 65, 75, 74
Operation: new = old + 6
Test: divisible by 19
If true: throw to monkey 2
If false: throw to monkey 0
Monkey 2:
Starting items: 79, 60, 97
Operation: new = old * old
Test: divisible by 13
If true: throw to monkey 1
If false: throw to monkey 3
Monkey 3:
Starting items: 74
Operation: new = old + 3
Test: divisible by 17
If true: throw to monkey 0
If false: throw to monkey 1
"""
monkeys =
file_for_day!.(11)
|> String.split("\n\n")
|> Map.new(fn monkey_input ->
[monkey_name_line, starting_items_line, operration_line, test_line, true_line, false_line] =
monkey_input |> String.split("\n", trim: true) |> Enum.map(&String.trim(&1))
monkey_id =
monkey_name_line
|> String.trim_trailing(":")
|> String.trim_leading("Monkey ")
|> String.to_integer()
starting_items =
starting_items_line
|> String.trim_leading("Starting items: ")
|> String.split(", ")
|> Enum.map(&String.to_integer(&1))
operation_string = operration_line |> String.trim_leading("Operation: new = ")
operation = fn old -> Code.eval_string(operation_string, old: old) |> elem(0) end
divisor = test_line |> String.trim_leading("Test: divisible by ") |> String.to_integer()
true_monkey =
true_line |> String.trim_leading("If true: throw to monkey ") |> String.to_integer()
false_monkey =
false_line |> String.trim_leading("If false: throw to monkey ") |> String.to_integer()
{monkey_id,
%{
items: starting_items,
operation: operation,
divisor: divisor,
true_monkey: true_monkey,
false_monkey: false_monkey,
counter: 0
}}
end)
lcm =
Enum.reduce(monkeys, 1, fn {_id, monkey}, lcm ->
lcm * monkey.divisor
end)
[a, b] =
Enum.reduce(1..10000, monkeys, fn _round, monkeys ->
Enum.reduce(0..(map_size(monkeys) - 1), monkeys, fn monkey_id, monkeys ->
Enum.reduce(monkeys[monkey_id].items, monkeys, fn _item, monkeys ->
current_monkey = monkeys[monkey_id]
{current_item, rest_of_items} = List.pop_at(current_monkey.items, 0)
monkeys = put_in(monkeys, [monkey_id, :items], rest_of_items)
monkeys = update_in(monkeys, [monkey_id, :counter], &(&1 + 1))
worry_level = current_monkey.operation.(current_item)
pass_to_monkey_id =
if Integer.mod(worry_level, current_monkey.divisor) == 0 do
current_monkey.true_monkey
else
current_monkey.false_monkey
end
update_in(monkeys, [pass_to_monkey_id, :items], &(&1 ++ [Integer.mod(worry_level, lcm)]))
end)
end)
end)
|> Enum.map(&elem(&1, 1).counter)
|> Enum.sort(:desc)
|> Enum.take(2)
a * b