Skip to content

Commit fd3a87a

Browse files
committed
Update day 13 to use DP
1 parent 5c1a1f4 commit fd3a87a

1 file changed

Lines changed: 64 additions & 52 deletions

File tree

lib/2015/day_13.ex

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,37 @@ defmodule AdventOfCode.Y2015.Day13 do
33
--- Day 13: Knights of the Dinner Table ---
44
Problem Link: https://adventofcode.com/2015/day/13
55
Difficulty: s
6-
Tags: brute-force combinatorics slow
6+
Tags: graph dynamic-programming
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
9-
10-
alias AdventOfCode.Algorithms.Combinatorics
9+
alias Yog.Model
10+
import Bitwise
1111

1212
def input, do: InputReader.read_from_file(2015, 13)
1313

1414
def run(input \\ input()) do
15-
{people, facts} = parse(input)
15+
graph = parse(input)
1616

1717
{
18-
maximize_happiness({people, facts}),
19-
happiness_with_me({people, facts})
18+
solve(graph),
19+
solve(add_me(graph))
2020
}
2121
end
2222

2323
def parse(data \\ input()) do
24-
facts =
24+
parsed =
2525
data
2626
|> Transformers.lines()
2727
|> Enum.map(&parse_fact/1)
28-
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
29-
|> Enum.flat_map(fn {mapset, [happiness_1, happiness_2]} ->
30-
[a, b] = MapSet.to_list(mapset)
31-
happiness = happiness_1 + happiness_2
3228

33-
[{{a, b}, happiness}, {{b, a}, happiness}]
29+
graph =
30+
Enum.reduce(parsed, Yog.undirected(), fn {a, b, _w}, g ->
31+
g |> Model.add_node(a, nil) |> Model.add_node(b, nil)
3432
end)
35-
|> Enum.into(%{})
36-
37-
people =
38-
facts
39-
|> Map.keys()
40-
|> Enum.flat_map(&Tuple.to_list/1)
41-
|> Enum.uniq()
4233

43-
{people, facts}
34+
Enum.reduce(parsed, graph, fn {a, b, w}, g ->
35+
Model.add_edge_with_combine!(g, a, b, w, &Kernel.+/2)
36+
end)
4437
end
4538

4639
@regex ~r/(\w+) would (gain|lose) (\d+) happiness units by sitting next to (\w+)\./
@@ -49,48 +42,67 @@ defmodule AdventOfCode.Y2015.Day13 do
4942
|> Regex.run(statement, capture: :all_but_first)
5043
|> then(fn [person_a, action, value, person_b] ->
5144
{
52-
MapSet.new([person_a, person_b]),
53-
String.to_integer(value) * ((action == "gain" && 1) || -1)
45+
person_a,
46+
person_b,
47+
String.to_integer(value) * if(action == "gain", do: 1, else: -1)
5448
}
5549
end)
5650
end
5751

58-
defp happiness_with_me({people, facts}) do
59-
facts =
60-
people
61-
|> Enum.flat_map(fn person ->
62-
[
63-
{{"Me", person}, 0},
64-
{{person, "Me"}, 0}
65-
]
66-
end)
67-
|> Enum.into(%{})
68-
|> Map.merge(facts)
52+
defp add_me(graph) do
53+
people = Model.all_nodes(graph)
6954

70-
people = ["Me" | people]
71-
72-
maximize_happiness({people, facts})
55+
Enum.reduce(people, graph, fn person, g ->
56+
Model.add_edge_ensure(g, "Me", person, 0)
57+
end)
7358
end
7459

75-
defp maximize_happiness({people, facts}) do
76-
people
77-
|> all_pairs()
78-
|> Enum.map(&collect_happiness(&1, facts))
79-
|> Enum.max()
60+
defp solve(graph) do
61+
nodes = Model.all_nodes(graph)
62+
[root | _rest] = nodes
63+
nodes_indexed = Enum.with_index(nodes)
64+
node_to_idx = Map.new(nodes_indexed)
65+
_idx_to_node = Map.new(Enum.map(nodes_indexed, fn {n, i} -> {i, n} end))
66+
67+
weights =
68+
for {u, i} <- nodes_indexed, {v, j} <- nodes_indexed, into: %{} do
69+
{{i, j}, edge_weight(graph, u, v)}
70+
end
71+
72+
root_idx = node_to_idx[root]
73+
num_nodes = length(nodes)
74+
target_mask = (1 <<< num_nodes) - 1
75+
76+
Process.put(:tsp_memo, %{})
77+
tsp(root_idx, 1 <<< root_idx, root_idx, target_mask, weights, num_nodes)
8078
end
8179

82-
defp all_pairs(people) do
83-
people
84-
|> Combinatorics.permutations()
85-
|> Enum.flat_map(fn [first | _] = people ->
86-
[people ++ [first]]
87-
end)
88-
|> Enum.map(&Enum.chunk_every(&1, 2, 1, :discard))
80+
defp tsp(curr_idx, mask, root_idx, target_mask, weights, n) do
81+
if mask == target_mask do
82+
weights[{curr_idx, root_idx}]
83+
else
84+
memo = Process.get(:tsp_memo)
85+
86+
if Map.has_key?(memo, {curr_idx, mask}) do
87+
memo[{curr_idx, mask}]
88+
else
89+
res =
90+
for next_idx <- 0..(n - 1), (mask &&& 1 <<< next_idx) == 0 do
91+
weights[{curr_idx, next_idx}] +
92+
tsp(next_idx, bor(mask, 1 <<< next_idx), root_idx, target_mask, weights, n)
93+
end
94+
|> Enum.max()
95+
96+
Process.put(:tsp_memo, Map.put(memo, {curr_idx, mask}, res))
97+
res
98+
end
99+
end
89100
end
90101

91-
defp collect_happiness(data, facts) do
92-
Enum.reduce(data, 0, fn [person_1, person_2], happiness ->
93-
happiness + facts[{person_1, person_2}]
94-
end)
102+
defp edge_weight(graph, u, v) do
103+
case Enum.find(Model.successors(graph, u), fn {id, _} -> id == v end) do
104+
{_, w} -> w
105+
nil -> 0
106+
end
95107
end
96108
end

0 commit comments

Comments
 (0)