summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/day16.exs194
-rw-r--r--src/day17.exs127
-rw-r--r--src/day18.exs94
-rw-r--r--src/day19.exs104
-rw-r--r--src/day20.exs122
-rw-r--r--src/day21.exs147
-rw-r--r--src/day22.exs65
-rw-r--r--src/day23.exs118
8 files changed, 971 insertions, 0 deletions
diff --git a/src/day16.exs b/src/day16.exs
new file mode 100644
index 0000000..827dc25
--- /dev/null
+++ b/src/day16.exs
@@ -0,0 +1,194 @@
+defmodule Day16 do
+ defmodule State do
+ @enforce_keys [:pqueue, :map, :path, :dist]
+ defstruct [:pqueue, :map, :path, :dist]
+
+ def new(game) do
+ pqueue = :gb_sets.from_list([{0, game.start}])
+ map = :gb_trees.insert(game.start, 0, :gb_trees.empty())
+ path = Map.new([{game.start, []}])
+ dist = Map.new([{game.start, 0}])
+ %State{pqueue: pqueue, map: map, path: path, dist: dist}
+ end
+
+ def upsert(state, posdir, dist, prev) do
+ case :gb_trees.lookup(posdir, state.map) do
+ :none ->
+ pqueue = :gb_sets.insert({dist, posdir}, state.pqueue)
+ map = :gb_trees.insert(posdir, dist, state.map)
+ path = Map.put(state.path, posdir, [prev])
+ distmap = Map.update(state.dist, posdir, dist, &min(&1, dist))
+ %State{pqueue: pqueue, map: map, path: path, dist: distmap}
+ {_, odist} ->
+ distmap = Map.update!(state.dist, posdir, &min(&1, dist))
+ cond do
+ dist > odist ->
+ %State{state | dist: distmap}
+ dist == odist ->
+ path = Map.update!(state.path, posdir, &[prev | &1])
+ %State{state | path: path, dist: distmap}
+ true ->
+ pqueue = :gb_sets.delete({odist, posdir}, state.pqueue)
+ pqueue = :gb_sets.insert({dist, posdir}, pqueue)
+ map = :gb_trees.update(posdir, dist, state.map)
+ path = Map.put(state.path, posdir, [prev])
+ %State{pqueue: pqueue, map: map, path: path, dist: distmap}
+ end
+ end
+ end
+
+ def empty?(state) do
+ :gb_sets.is_empty(state.pqueue)
+ end
+
+ def pop(state) do
+ {v, pqueue} = :gb_sets.take_smallest(state.pqueue)
+ {v, %State{state | pqueue: pqueue}}
+ end
+
+ def optimal_dist(state, node) do
+ state.dist
+ |> Enum.filter(fn {{pos, _}, _} -> pos == node end)
+ |> Enum.map(fn {_, v} -> v end)
+ |> Enum.min()
+ end
+
+ def optimal_path_nodes(state, node) do
+ nodes = state.dist
+ |> Enum.filter(fn {{pos, _}, _} -> pos == node end)
+
+ mindist = nodes
+ |> Enum.map(fn {_, v} -> v end)
+ |> Enum.min()
+
+ nodes = nodes
+ |> Enum.filter(fn {_, v} -> v == mindist end)
+ |> Enum.map(fn {posdir, _} -> posdir end)
+
+ optimal_path_nodes(state, nodes, [])
+ end
+
+ defp optimal_path_nodes(state, queue, acc) do
+ case queue do
+ [] ->
+ acc
+ |> Enum.map(fn {pos, _} -> pos end)
+ |> Enum.uniq()
+ [h | t] ->
+ acc = [h | acc]
+ queue = Map.fetch!(state.path, h) ++ t
+ optimal_path_nodes(state, queue, acc)
+ end
+ end
+ end
+
+ defmodule Game do
+ @derive Inspect
+ defstruct [:grid, :start, :end]
+
+ def from_data(data) do
+ grid = data
+ |> String.split("\n", trim: true)
+ |> Enum.with_index()
+ |> Enum.flat_map(fn {s, i} ->
+ String.codepoints(s)
+ |> Enum.with_index()
+ |> Enum.map(fn {c, j} -> {{i, j}, c} end)
+ end)
+ |> Map.new()
+
+ start = grid
+ |> Enum.find_value(fn {pos, c} -> c == "S" and pos end)
+ |> then(& {&1, :right})
+
+ endpos = grid
+ |> Enum.find_value(fn {pos, c} -> c == "E" and pos end)
+
+ %Game{grid: grid, start: start, end: endpos}
+ end
+
+ def shortest(game) do
+ game
+ |> shortest(State.new(game))
+ |> State.optimal_dist(game.end)
+ end
+
+ def shortest_path_nodes(game) do
+ game
+ |> shortest(State.new(game))
+ |> State.optimal_path_nodes(game.end)
+ end
+
+ defp shortest(game, state) do
+ if State.empty?(state) do
+ state
+ else
+ {{dist, {pos, dir}}, state} = State.pop(state)
+ if pos == game.end do
+ shortest(game, state)
+ else
+ neighbors(game, {dist, {pos, dir}})
+ for {ndist, nposdir} <- neighbors(game, {dist, {pos, dir}}), reduce: state do
+ state -> State.upsert(state, nposdir, ndist, {pos, dir})
+ end
+ |> then(& shortest(game, &1))
+ end
+ end
+ end
+
+ defp neighbors(game, {dist, {{i, j}, dir}}) do
+ case dir do
+ :right ->
+ [{dist+1, {{i, j+1}, :right}},
+ {dist+1001, {{i-1, j}, :up}},
+ {dist+1001, {{i+1, j}, :down}},
+ {dist+2001, {{i, j-1}, :left}}]
+ :left ->
+ [{dist+1, {{i, j-1}, :left}},
+ {dist+1001, {{i-1, j}, :up}},
+ {dist+1001, {{i+1, j}, :down}},
+ {dist+2001, {{i, j+1}, :right}}]
+ :up ->
+ [{dist+1, {{i-1, j}, :up}},
+ {dist+1001, {{i, j-1}, :left}},
+ {dist+1001, {{i, j+1}, :right}},
+ {dist+2001, {{i+1, j}, :down}}]
+ :down ->
+ [{dist+1, {{i+1, j}, :down}},
+ {dist+1001, {{i, j-1}, :left}},
+ {dist+1001, {{i, j+1}, :right}},
+ {dist+2001, {{i-1, j}, :up}}]
+ end
+ |> Enum.filter(fn {_, {p, _}} ->
+ case Map.fetch(game.grid, p) do
+ :error -> false
+ {:ok, "#"} -> false
+ _ -> true
+ end
+ end)
+ end
+ end
+
+ def part1(data) do
+ data
+ |> Game.from_data()
+ |> Game.shortest()
+ end
+
+ def part2(data) do
+ data
+ |> Game.from_data()
+ |> Game.shortest_path_nodes()
+ |> Enum.count()
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day16.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}")
+
+{time2, ans2} = :timer.tc(fn -> Day16.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}")
diff --git a/src/day17.exs b/src/day17.exs
new file mode 100644
index 0000000..9f9db29
--- /dev/null
+++ b/src/day17.exs
@@ -0,0 +1,127 @@
+defmodule Day17 do
+ defmodule Computer do
+ @derive Inspect
+ @enforce_keys [:a, :b, :c, :out, :instructions, :pointer]
+ defstruct @enforce_keys
+
+ def from_data(data) do
+ [a, b, c | instructions] = Regex.scan(~r/\d+/, data)
+ |> Enum.map(&String.to_integer(hd(&1)))
+
+ %Computer{a: a, b: b, c: c, out: [], instructions: instructions, pointer: 0}
+ end
+
+ def run(comp) do
+ case instruction(comp) do
+ {:halt, comp} ->
+ comp
+ {:ok, comp} ->
+ run(comp)
+ end
+ end
+
+ defp instruction(comp) do
+ case comp.instructions |> Enum.drop(comp.pointer) do
+ [] ->
+ {:halt, comp}
+ [0, r | _] ->
+ cr = combo(comp, r)
+ {:ok, %Computer{comp | a: Bitwise.bsr(comp.a, cr), pointer: comp.pointer+2}}
+ [1, r | _] ->
+ {:ok, %Computer{comp | b: Bitwise.bxor(comp.b, r), pointer: comp.pointer+2}}
+ [2, r | _] ->
+ cr = combo(comp, r)
+ {:ok, %Computer{comp | b: rem(cr, 8), pointer: comp.pointer+2}}
+ [3, r | _] ->
+ if comp.a == 0 do
+ {:ok, %Computer{comp | pointer: comp.pointer+2}}
+ else
+ {:ok, %Computer{comp | pointer: r}}
+ end
+ [4, _ | _] ->
+ {:ok, %Computer{comp | b: Bitwise.bxor(comp.b, comp.c), pointer: comp.pointer+2}}
+ [5, r | _] ->
+ cr = combo(comp, r)
+ {:ok, %Computer{comp | out: [rem(cr, 8) | comp.out], pointer: comp.pointer+2}}
+ [6, r | _] ->
+ cr = combo(comp, r)
+ {:ok, %Computer{comp | b: Bitwise.bsr(comp.a, cr), pointer: comp.pointer+2}}
+ [7, r | _] ->
+ cr = combo(comp, r)
+ {:ok, %Computer{comp | c: Bitwise.bsr(comp.a, cr), pointer: comp.pointer+2}}
+ end
+ end
+
+ defp combo(comp, v) do
+ case v do
+ 0 -> 0
+ 1 -> 1
+ 2 -> 2
+ 3 -> 3
+ 4 -> comp.a
+ 5 -> comp.b
+ 6 -> comp.c
+ 7 -> raise "Invalid combo operand (7)"
+ end
+ end
+ end
+
+ def part1(data) do
+ data
+ |> Computer.from_data()
+ |> Computer.run()
+ |> then(& &1.out)
+ |> Enum.reverse()
+ |> Enum.join(",")
+ end
+
+ def part2(data) do
+ data
+ |> Computer.from_data()
+ |> then(& &1.instructions)
+ |> Enum.reverse()
+ |> solve2(0)
+ end
+
+ defp solve2(rout, a) do
+ case rout do
+ [] ->
+ a
+ [h | t] ->
+ # I deduced the following by hand for my input, but maybe it could
+ # have been done programatically. An important takeway is that
+ # (for my input, and probably for all) `a` incerases by 3 bits for each
+ # iteration of the program. AS such, we can only need to check the
+ # possibilities over the last 3 bits (a..a+7) in each iteration ourselves
+ a = Bitwise.bsl(a, 3)
+ possible = a..a+7
+ |> Enum.filter(fn a ->
+ Bitwise.band(a, 7)
+ |> Bitwise.bxor(5)
+ |> Bitwise.bxor(6)
+ |> Bitwise.bxor(Bitwise.bsr(a, Bitwise.band(a, 7) |> Bitwise.bxor(5)))
+ |> Bitwise.band(7) == h
+ end)
+
+ case possible do
+ [] ->
+ :none
+ possible ->
+ possible
+ |> Enum.map(& solve2(t, &1))
+ |> Enum.find(& &1 != :none)
+ || :none
+ end
+ end
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day17.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}")
+
+{time2, ans2} = :timer.tc(fn -> Day17.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}")
diff --git a/src/day18.exs b/src/day18.exs
new file mode 100644
index 0000000..e97da36
--- /dev/null
+++ b/src/day18.exs
@@ -0,0 +1,94 @@
+defmodule Day18 do
+ defmodule Game do
+ @derive Inspect
+ @enforce_keys [:obj, :source, :target, :w, :h]
+ defstruct @enforce_keys
+
+ def from_data(data, source, target) do
+ obj = data
+ |> String.split("\n", trim: true)
+ |> Enum.map(&integer_list/1)
+ |> Enum.map(&List.to_tuple/1)
+
+ {w, h} = target
+
+ %Game{obj: obj, source: source, target: target, w: w, h: h}
+ end
+
+ defp integer_list(s), do: String.split(s, ",") |> Enum.map(&String.to_integer/1)
+
+ def shortest(game, count_obj \\ nil) do
+ obj =
+ if count_obj == nil do
+ game.obj
+ else
+ Enum.take(game.obj, count_obj)
+ end
+
+ %Game{game | obj: MapSet.new(obj)}
+ |> shortest(0, [game.source], MapSet.new([game.source]))
+ end
+
+ defp shortest(game, i, q, v) do
+ case q do
+ [] ->
+ :none
+ q ->
+ if Enum.member?(q, game.target) do
+ i
+ else
+ for n <- q, reduce: {[], v} do
+ {q, v} ->
+ neighbors(game, n)
+ |> Enum.reject(&MapSet.member?(v, &1))
+ |> Enum.reduce({q, v}, fn n, {q, v} -> {[n | q], MapSet.put(v, n)} end)
+ end
+ |> then(fn {q, v} -> shortest(game, i+1, q, v) end)
+ end
+ end
+ end
+
+ defp neighbors(game, {x, y}) do
+ [{x+1, y}, {x-1, y}, {x, y+1}, {x, y-1}]
+ |> Enum.filter(fn {x, y} -> x >= 0 and x <= game.w and y >= 0 and y <= game.h end)
+ |> Enum.reject(&MapSet.member?(game.obj, &1))
+ end
+ end
+
+ def part1(data) do
+ data
+ |> Game.from_data({0, 0}, {70, 70})
+ |> Game.shortest(1024)
+ end
+
+ def part2(data) do
+ game = data
+ |> Game.from_data({0, 0}, {70, 70})
+
+ part2(game, 1, Enum.count(game.obj))
+ end
+
+ defp part2(game, l, r) do
+ if l == r do
+ Enum.at(game.obj, l-1)
+ |> then(fn {x, y} -> "#{x},#{y}" end)
+ else
+ m = div(l + r, 2)
+ if Game.shortest(game, m) != :none do
+ part2(game, m+1, r)
+ else
+ part2(game, l, m)
+ end
+ end
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day18.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}")
+
+{time2, ans2} = :timer.tc(fn -> Day18.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}")
diff --git a/src/day19.exs b/src/day19.exs
new file mode 100644
index 0000000..9c9a1e2
--- /dev/null
+++ b/src/day19.exs
@@ -0,0 +1,104 @@
+defmodule Day19 do
+ defmodule Onsen do
+ @derive Inspect
+ @enforce_keys [:patterns, :designs]
+ defstruct @enforce_keys
+
+ def from_data(data) do
+ [patterns, designs] = data
+ |> String.split("\n\n", trim: true)
+
+ patterns = patterns
+ |> String.split(", ", trim: true)
+
+ designs = designs
+ |> String.split("\n", trim: true)
+
+ %Onsen{patterns: patterns, designs: designs}
+ end
+
+ def count_feasible_designs(onsen) do
+ onsen.designs
+ |> Enum.filter(& feasible?(&1, onsen.patterns) |> elem(0))
+ |> Enum.count()
+ end
+
+ defp feasible?(design, patterns, vis \\ MapSet.new()) do
+ cond do
+ MapSet.member?(vis, design) ->
+ {false, vis}
+ design == "" ->
+ {true, vis}
+ true ->
+ for pat <- patterns, reduce: {false, MapSet.put(vis, design)} do
+ {acc, vis} ->
+ cond do
+ acc == true ->
+ {acc, vis}
+ String.starts_with?(design, pat) ->
+ design
+ |> String.split_at(String.length(pat))
+ |> elem(1)
+ |> feasible?(patterns, vis)
+ true ->
+ {acc, vis}
+ end
+ end
+ end
+ end
+
+ def count_feasible_ways(onsen) do
+ onsen.designs
+ |> Enum.map(& ways(&1, onsen.patterns) |> elem(0))
+ |> Enum.sum()
+ end
+
+ defp ways(design, patterns, vis \\ Map.new()) do
+ case Map.fetch(vis, design) do
+ {:ok, count} ->
+ {count, vis}
+ :error ->
+ if design == "" do
+ {1, vis}
+ else
+ for pat <- patterns, reduce: {0, vis} do
+ {acc, vis} ->
+ cond do
+ String.starts_with?(design, pat) ->
+ design
+ |> String.split_at(String.length(pat))
+ |> elem(1)
+ |> ways(patterns, vis)
+ |> then(fn {acc2, vis} -> {acc + acc2, vis} end)
+ true ->
+ {acc, vis}
+ end
+ end
+ |> then(fn {acc, vis} -> {acc, Map.put(vis, design, acc)} end)
+ end
+ end
+ end
+ end
+
+ def part1(data) do
+ data
+ |> Onsen.from_data()
+ |> Onsen.count_feasible_designs()
+ end
+
+ def part2(data) do
+ data
+ |> Onsen.from_data()
+ |> Onsen.count_feasible_ways()
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day19.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}")
+
+{time2, ans2} = :timer.tc(fn -> Day19.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}")
diff --git a/src/day20.exs b/src/day20.exs
new file mode 100644
index 0000000..8299199
--- /dev/null
+++ b/src/day20.exs
@@ -0,0 +1,122 @@
+defmodule Day20 do
+ defmodule Maze do
+ @derive Inspect
+ @enforce_keys [:positions, :walls, :source, :target]
+ defstruct @enforce_keys
+
+ def from_data(data) do
+ positions = data
+ |> String.split("\n", trim: true)
+ |> Enum.with_index()
+ |> Enum.map(fn {l, i} ->
+ l
+ |> String.codepoints()
+ |> Enum.with_index()
+ |> Enum.map(fn {v, j} -> {{i, j}, v} end)
+ end)
+ |> List.flatten()
+
+ source = Enum.find(positions, fn {_, v} -> v == "S" end) |> elem(0)
+ target = Enum.find(positions, fn {_, v} -> v == "E" end) |> elem(0)
+
+ walls = positions
+ |> Enum.filter(fn {_, v} -> v == "#" end)
+ |> Enum.map(fn {p, _} -> p end)
+
+ positions = positions
+ |> Enum.filter(fn {_, v} -> v != "#" end)
+ |> Enum.map(fn {p, _} -> p end)
+ |> MapSet.new()
+
+ %Maze{positions: positions, walls: walls, source: source, target: target}
+ end
+
+ def cheats(maze, seconds) do
+ # Get shortest distances for all nodes
+ dist = rev_shortest(maze)
+
+ # Get cheats
+ for n1 <- maze.positions, n2 <- neighbors(maze, n1, seconds) do
+ {n1, n2, Map.get(dist, n1) - Map.get(dist, n2) - dist(n1, n2)}
+ end
+ end
+
+ defp dist({x1, y1}, {x2, y2}), do: abs(x1-x2) + abs(y1-y2)
+
+ defp rev_shortest(maze) do
+ maze.target
+ |> then(&rev_shortest(maze, :queue.from_list([{&1, 0}]), MapSet.new([&1]), Map.new()))
+ end
+
+ defp rev_shortest(maze, q, inq, dist) do
+ case :queue.peek(q) do
+ :empty ->
+ dist
+ {_, {v, d}} ->
+ for n <- neighbors(maze, v), reduce: {:queue.drop(q), inq} do
+ {q, inq} ->
+ if MapSet.member?(inq, n) do
+ {q, inq}
+ else
+ {:queue.in({n, d+1}, q), MapSet.put(inq, n)}
+ end
+ end
+ |> then(fn {q, inq} -> rev_shortest(maze, q, inq, Map.put(dist, v, d)) end)
+ end
+ end
+
+ defp neighbors(maze, {x, y}) do
+ [{x+1, y},
+ {x-1, y},
+ {x, y+1},
+ {x, y-1}]
+ |> Enum.filter(&MapSet.member?(maze.positions, &1))
+ end
+
+ defp neighbors(maze, {x, y}, d) do
+ for i <- 0..d, j <- 0..d-i do
+ cond do
+ i == 0 and j == 0 ->
+ []
+ i == 0 ->
+ [{x, y+j},
+ {x, y-j}]
+ j == 0 ->
+ [{x+i, y},
+ {x-i, y}]
+ true ->
+ [{x+i, y+j},
+ {x-i, y+j},
+ {x+i, y-j},
+ {x-i, y-j}]
+ end
+ end
+ |> List.flatten()
+ |> Enum.filter(&MapSet.member?(maze.positions, &1))
+ end
+ end
+
+ def part1(data) do
+ data
+ |> Maze.from_data()
+ |> Maze.cheats(2)
+ |> Enum.count(& elem(&1, 2) >= 100)
+ end
+
+ def part2(data) do
+ data
+ |> Maze.from_data()
+ |> Maze.cheats(20)
+ |> Enum.count(& elem(&1, 2) >= 100)
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day20.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}")
+
+{time2, ans2} = :timer.tc(fn -> Day20.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}")
diff --git a/src/day21.exs b/src/day21.exs
new file mode 100644
index 0000000..8c2e0ce
--- /dev/null
+++ b/src/day21.exs
@@ -0,0 +1,147 @@
+defmodule Day21 do
+ defmodule Keypad do
+ @derive Inspect
+ @enforce_keys [:type]
+ defstruct @enforce_keys
+
+ def new(type) do
+ %Keypad{type: type}
+ end
+
+ def shortest_paths(keypad, from, to) do
+ bfs([[{:nil, from}]], to, keypad)
+ end
+
+ defp bfs(paths, to, keypad) do
+ finished = Enum.any?(paths, &(elem(hd(&1), 1) == to))
+
+ if finished do
+ Enum.filter(paths, &(elem(hd(&1), 1) == to))
+ else
+ for path <- paths, neighbor <- neighbors(keypad, elem(hd(path), 1)) do
+ [neighbor | path]
+ end
+ |> bfs(to, keypad)
+ end
+ end
+
+ defp neighbors(keypad, from) do
+ case keypad.type do
+ :numeric -> neighbors_numeric(from)
+ :directional -> neighbors_directional(from)
+ end
+ end
+
+ defp neighbors_numeric(from) do
+ case from do
+ "A" -> %{left: "0", up: "3"}
+ "0" -> %{right: "A", up: "2"}
+ "1" -> %{right: "2", up: "4"}
+ "2" -> %{down: "0", left: "1", right: "3", up: "5"}
+ "3" -> %{down: "A", left: "2", up: "6"}
+ "4" -> %{down: "1", right: "5", up: "7"}
+ "5" -> %{down: "2", left: "4", right: "6", up: "8"}
+ "6" -> %{down: "3", left: "5", up: "9"}
+ "7" -> %{down: "4", right: "8"}
+ "8" -> %{down: "5", left: "7", right: "9"}
+ "9" -> %{down: "6", left: "8"}
+ end
+ end
+
+ defp neighbors_directional(from) do
+ case from do
+ :A -> %{left: :up, down: :right}
+ :up -> %{right: :A, down: :down}
+ :left -> %{right: :down}
+ :down -> %{left: :left, up: :up, right: :right}
+ :right -> %{left: :down, up: :A}
+ end
+ end
+ end
+
+ def part1(data, n \\ 2) do
+ data
+ |> String.split("\n", trim: true)
+ |> Enum.map(fn code ->
+ {
+ String.split(code, "A") |> hd() |> String.to_integer(),
+ shortest_dist(String.codepoints(code), "A", n, %{}) |> elem(0)
+ }
+ end)
+ # |> IO.inspect(charlists: :as_lists)
+ |> Enum.map(fn {a, b} -> a*b end)
+ |> Enum.sum()
+ end
+
+ defp shortest_dist(code, start, n, state) do
+ chunks = [start | code]
+ |> Enum.chunk_every(2, 1, :discard)
+
+ kpadtype = if start == "A" do :numeric else :directional end
+
+ {values, nstate} =
+ for [from, to] <- chunks, reduce: {[], state} do
+ {values, state} ->
+ shortest_dist(from, to, n, kpadtype, state)
+ |> then(fn {v, state} -> {[v | values], state} end)
+ end
+
+ {Enum.sum(values), nstate}
+ end
+
+ defp shortest_dist(from, to, n, keypadtype, state) do
+ key = {from, to, n, keypadtype}
+ case Map.get(state, key) do
+ :nil ->
+ {values, nstate} =
+ for path <- paths(from, to, keypadtype), reduce: {[], state} do
+ {values, state} -> shortest_dist(path, n, state)
+ |> then(fn {v, s} -> {[v | values], s} end)
+ end
+ value = Enum.min(values)
+ {value, Map.put(nstate, key, value)}
+ value ->
+ {value, state}
+ end
+ end
+
+ defp paths(from, to, keypad) do
+ case keypad do
+ :numeric ->
+ Keypad.new(:numeric)
+ :directional ->
+ Keypad.new(:directional)
+ end
+ |> Keypad.shortest_paths(from, to)
+ end
+
+ defp shortest_dist(path, n, state) do
+ if n == 0 do
+ {Enum.count(path), state}
+ else
+ path
+ |> Enum.map(&(elem(&1, 0)))
+ |> then(&([:A | &1]))
+ |> Enum.reverse()
+ |> tl()
+ |> shortest_dist(:A, n-1, state)
+ end
+ end
+
+ def part2(data) do
+ part1(data, 25)
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day21.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}") # 184718
+
+{time2, ans2} = :timer.tc(fn -> Day21.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}") # 228800606998554
+
+
+
diff --git a/src/day22.exs b/src/day22.exs
new file mode 100644
index 0000000..418631f
--- /dev/null
+++ b/src/day22.exs
@@ -0,0 +1,65 @@
+defmodule Day22 do
+ def part1(data, n \\ 2000) do
+ data
+ |> String.split("\n", trim: true)
+ |> Enum.map(&String.to_integer/1)
+ |> Enum.map(&(secret_numbers(&1, n)))
+ |> Enum.map(&hd/1)
+ |> Enum.sum()
+ end
+
+ defp secret_numbers(v, n) do
+ for _ <- 1..n, reduce: [v] do
+ v ->
+ [(v |> hd() |> change_value(6) |> change_value(-5) |> change_value(11)) | v]
+ end
+ end
+
+ defp change_value(v, shift) do
+ v
+ |> Bitwise.bsl(shift)
+ |> Bitwise.bxor(v)
+ |> Integer.mod(16777216)
+ end
+
+ def part2(data, n \\ 2000) do
+ data
+ |> String.split("\n", trim: true)
+ |> Enum.map(&String.to_integer/1)
+ |> Enum.map(&(secret_numbers(&1, n)))
+ |> Enum.map(&(sequences(&1, 10)))
+ |> Enum.reduce(%{}, fn s, acc ->
+ Map.merge(acc, s, fn _k, v1, v2 -> v1 + v2 end)
+ end)
+ |> Map.values()
+ |> Enum.max()
+ end
+
+ defp sequences(vs, m) do
+ vs
+ |> Enum.map(&Integer.mod(&1, m))
+ |> Enum.chunk_every(5, 1, :discard) # performance here could be improved
+ |> Enum.map(fn chunk -> {diffs(chunk), hd(chunk)} end)
+ |> Map.new()
+ end
+
+ defp diffs(vs) do
+ vs
+ |> Enum.chunk_every(2, 1, :discard)
+ |> Enum.map(fn [a, b] -> a - b end)
+ |> List.to_tuple()
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day22.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}") # 19822877190
+
+{time2, ans2} = :timer.tc(fn -> Day22.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}") # 2277
+
+
+
diff --git a/src/day23.exs b/src/day23.exs
new file mode 100644
index 0000000..04d60ce
--- /dev/null
+++ b/src/day23.exs
@@ -0,0 +1,118 @@
+defmodule Day23 do
+ defmodule Graph do
+ @derive Inspect
+ @enforce_keys [:nodes, :edges, :adj, :deg]
+ defstruct @enforce_keys
+
+ def from_data(data) do
+ edges = data
+ |> String.split("\n", trim: true)
+ |> Enum.map(&String.split(&1, "-", trim: true))
+ |> Enum.map(fn [a,b] -> if a < b do [a, b] else [b, a] end end)
+
+ nodes = edges
+ |> List.flatten()
+ |> Enum.sort()
+ |> Enum.dedup()
+
+ adj =
+ for [a, b] <- edges, reduce: %{} do
+ acc ->
+ add_adj(acc, a, b)
+ |> add_adj(b, a)
+ end
+ |> Enum.map(fn {k, v} -> {k, MapSet.new(v)} end)
+ |> Map.new()
+
+ deg = Enum.map(adj, fn {k, v} -> {k, Enum.count(v)} end) |> Map.new()
+
+ %Graph{edges: edges, nodes: nodes, adj: adj, deg: deg}
+ end
+
+ defp add_adj(adj, a, b) do
+ Map.get_and_update(adj, a, fn v ->
+ case v do
+ nil -> {v, [b]}
+ lst -> {v, [b | lst]}
+ end
+ end)
+ |> elem(1)
+ end
+
+ def cliques(graph, n) do
+ cliques([], graph.adj, [], MapSet.new(graph.nodes), n)
+ |> Enum.map(&Enum.sort/1)
+ |> Enum.sort()
+ |> Enum.dedup()
+ end
+
+ # Could be improved, but this is fast enough
+ defp cliques(acc, adj, nodes, possible, n) do
+ if n == 0 do
+ [nodes | acc]
+ else
+ for v <- possible, reduce: acc do
+ acc ->
+ cliques(acc, adj, [v | nodes], MapSet.intersection(Map.get(adj, v), possible), n-1)
+ end
+ end
+ end
+
+ def maxclique(graph) do
+ bk([], graph.adj, graph.deg, [], MapSet.new(graph.nodes), MapSet.new())
+ |> Enum.max_by(&Enum.count/1)
+ end
+
+ defp bk(acc, adj, deg, r, p, x) do
+ if Enum.empty?(p) && Enum.empty?(x) do
+ [r | acc]
+ else
+ u = Enum.max_by(MapSet.union(p, x), fn u -> Map.get(deg, u) end)
+ nu = Map.get(adj, u)
+ np = MapSet.difference(p, nu)
+ for v <- np, reduce: {acc, p, x} do
+ {acc, p, x} ->
+ {
+ bk(
+ acc,
+ adj,
+ deg,
+ [v | r],
+ MapSet.intersection(p, Map.get(adj, v)),
+ MapSet.intersection(x, Map.get(adj, v))
+ ),
+ MapSet.delete(p, v),
+ MapSet.put(x, v)
+ }
+ end
+ |> elem(0)
+ end
+ end
+ end
+
+ def part1(data) do
+ data
+ |> Graph.from_data()
+ |> Graph.cliques(3)
+ |> Enum.filter(fn nodes -> Enum.any?(nodes, fn v -> String.starts_with?(v, "t") end) end)
+ |> Enum.count()
+ end
+
+ def part2(data) do
+ data
+ |> Graph.from_data()
+ |> Graph.maxclique()
+ |> Enum.sort()
+ |> Enum.join(",")
+ end
+end
+
+data = IO.read(:stdio, :eof)
+
+{time1, ans1} = :timer.tc(fn -> Day23.part1(data) end)
+IO.puts("Time : #{time1 / 1000000}")
+IO.puts("Answer: #{ans1}") # 1046
+
+{time2, ans2} = :timer.tc(fn -> Day23.part2(data) end)
+IO.puts("Time : #{time2 / 1000000}")
+IO.puts("Answer: #{ans2}") # de,id,ke,ls,po,sn,tf,tl,tm,uj,un,xw,yz