summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandre Jesus <adbjesus@gmail.com>2025-07-20 13:47:08 +0100
committerAlexandre Jesus <adbjesus@gmail.com>2025-07-20 13:47:08 +0100
commit72dc64dfc149b8257294e882eb64d60540afd0ad (patch)
treeae3cd33384850168428af4749fd1554c03b51211
parent9a8898a9d7ee0313da99232a58ca1d143b287d6a (diff)
downloadaoc2024-72dc64dfc149b8257294e882eb64d60540afd0ad.tar.gz
aoc2024-72dc64dfc149b8257294e882eb64d60540afd0ad.zip
-rw-r--r--src/day23.exs118
1 files changed, 118 insertions, 0 deletions
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