From c1125d6ede53a691f94f6f7dc8bdb3d03da0f5a9 Mon Sep 17 00:00:00 2001
From: Sloane Perrault <sloane.perrault@gmail.com>
Date: Wed, 21 Sep 2022 09:19:53 -0400
Subject: [PATCH] solve 2015 day 13

---
 2015/README.md      |  13 +++---
 2015/lib/2015/13.ex | 100 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 107 insertions(+), 6 deletions(-)
 create mode 100644 2015/lib/2015/13.ex

diff --git a/2015/README.md b/2015/README.md
index 32fbaa1..c6c37e7 100644
--- a/2015/README.md
+++ b/2015/README.md
@@ -12,12 +12,12 @@
   ```
 </details>
 
-|  S  |  M  |  T  |  W  |  T   |  F   |  S   |
-| :-: | :-: | :-: | :-: | :-:  | :-:  | :-:  |
-|     |     | [1] | [2] | [3]  | [4]  | [5]  |
-| [6] | [7] | [8] | [9] | [10] | [11] | [12] |
-| 13  | 14  | 15  | 16  | 17   | 18   | 19   |
-| 20  | 21  | 22  | 23  | 24   | 25   |      |
+|   S  |  M  |  T  |  W  |  T   |  F   |  S   |
+| :--: | :-: | :-: | :-: | :-:  | :-:  | :-:  |
+|      |     | [1] | [2] | [3]  | [4]  | [5]  |
+|  [6] | [7] | [8] | [9] | [10] | [11] | [12] |
+| [13] | 14  | 15  | 16  | 17   | 18   | 19   |
+|  20  | 21  | 22  | 23  | 24   | 25   |      |
 
 [1]: ./lib/2015/1.ex
 [2]: ./lib/2015/2.ex
@@ -31,3 +31,4 @@
 [10]: ./lib/2015/10.ex
 [11]: ./lib/2015/11.ex
 [12]: ./lib/2015/12.ex
+[13]: ./lib/2015/13.ex
diff --git a/2015/lib/2015/13.ex b/2015/lib/2015/13.ex
new file mode 100644
index 0000000..1c7d299
--- /dev/null
+++ b/2015/lib/2015/13.ex
@@ -0,0 +1,100 @@
+import AOC
+
+aoc 2015, 13 do
+  import NimbleParsec
+
+  def p1 do
+    {guests, edges} = parse_input()
+
+    permutations(guests)
+    |> Enum.map(&happiness(edges, &1))
+    |> Enum.max()
+  end
+
+  def p2 do
+    {guests, edges} = parse_input()
+
+    guests = guests ++ ["Zach"]
+
+    permutations(guests)
+    |> Enum.map(&happiness(edges, &1))
+    |> Enum.max()
+  end
+
+  defparsec(
+    :fact_parsec,
+    ascii_string([?a..?z, ?A..?Z], min: 1)
+    |> ignore(string(" would "))
+    |> choice([
+      string("gain"),
+      string("lose")
+    ])
+    |> ignore(string(" "))
+    |> integer(min: 1)
+    |> ignore(string(" happiness units by sitting next to "))
+    |> ascii_string([?a..?z, ?A..?Z], min: 1)
+    |> ignore(string("."))
+  )
+
+  def parse_fact(str) do
+    {:ok, [left, sign, amount, right], _, _, _, _} = fact_parsec(str)
+
+    {{left, right}, edge_value(sign, amount)}
+  end
+
+  def edge_value("gain", v), do: v
+  def edge_value("lose", v), do: 0 - v
+
+  def sort({a, b} = p) when a > b, do: swap(p)
+  def sort(p), do: p
+  def swap({a, b}), do: {b, a}
+
+  def parse_input() do
+    edges =
+      input_stream()
+      |> Stream.map(&parse_fact/1)
+      |> Enum.into(%{})
+
+    guests =
+      edges
+      |> Map.keys()
+      |> Enum.reduce(MapSet.new(), fn {a, b}, acc -> acc |> MapSet.put(a) |> MapSet.put(b) end)
+
+    edges =
+      for {pair, weight} <- edges, reduce: %{} do
+        acc ->
+          sorted = sort(pair)
+
+          if Map.has_key?(acc, sorted) do
+            acc
+          else
+            reverse = swap(pair)
+            Map.put(acc, sorted, weight + Map.get(edges, reverse))
+          end
+      end
+
+    guests = Enum.to_list(guests)
+
+    {guests, edges}
+  end
+
+  def permutations([]), do: [[]]
+
+  def permutations(xs) do
+    for h <- xs, t <- permutations(xs -- [h]), do: [h | t]
+  end
+
+  def happiness(edges, [a | _] = xs) do
+    happiness(edges, xs, a, 0)
+  end
+
+  def happiness(edges, [t], h, acc) do
+    edge = Map.get(edges, sort({h, t}), 0)
+    edge + acc
+  end
+
+  def happiness(edges, [a, b | rest], h, acc) do
+    edge = Map.get(edges, sort({a, b}), 0)
+    happiness(edges, [b | rest], h, acc + edge)
+  end
+end