Skip to content

Commit 0851ead

Browse files
committed
Solve 2017/12, 2022/24, 2024/5
1 parent 1dc7107 commit 0851ead

3 files changed

Lines changed: 196 additions & 72 deletions

File tree

lib/2017/day_12.ex

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,71 +3,45 @@ defmodule AdventOfCode.Y2017.Day12 do
33
--- Day 12: Digital Plumber ---
44
Problem Link: https://adventofcode.com/2017/day/12
55
Difficulty: s
6-
Tags: disjoint-set
6+
Tags: graph connectivity
77
"""
8-
alias AdventOfCode.Algorithms.DisjointSet
98
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
alias Yog.Connectivity
10+
alias Yog.Traversal
1011

1112
def input, do: InputReader.read_from_file(2017, 12)
1213

1314
def run(input \\ input()) do
14-
disjoint_set = input |> parse() |> create_disjoint_set()
15-
nodes = Map.keys(disjoint_set.parents)
15+
graph = parse(input)
1616

17-
{run_1(disjoint_set, nodes), run_2(disjoint_set, nodes)}
17+
{run_1(graph), run_2(graph)}
1818
end
1919

20-
defp run_1(disjoint_set, nodes) do
21-
disjoint_set
22-
|> find_connections_for(nodes, 0)
23-
|> length()
24-
end
25-
26-
defp run_2(disjoint_set, nodes) do
27-
disjoint_set
28-
|> find_groups(nodes)
20+
defp run_1(graph) do
21+
# Size of the connected component containing node 0
22+
graph
23+
|> Traversal.walk(0, :breadth_first)
2924
|> Enum.count()
3025
end
3126

32-
defp find_connections_for(disjoint_set, nodes, node) do
33-
{target_parent, disjoint_set} = DisjointSet.find(disjoint_set, node)
34-
35-
nodes
36-
|> Enum.reduce({[], disjoint_set}, fn x, {lst, set} ->
37-
case DisjointSet.find(set, x) do
38-
{^target_parent, new_set} -> {[x | lst], new_set}
39-
{_, new_set} -> {lst, new_set}
40-
end
41-
end)
42-
|> elem(0)
43-
end
44-
45-
defp create_disjoint_set(input) do
46-
disjoint_set = DisjointSet.new(Enum.count(input))
47-
48-
input
49-
|> Enum.reduce(disjoint_set, fn {node, conns}, acc ->
50-
Enum.reduce(conns, acc, fn y, s ->
51-
DisjointSet.union(s, node, y)
52-
end)
53-
end)
54-
end
55-
56-
defp find_groups(disj_set, nodes) do
57-
nodes
58-
|> Enum.reduce({MapSet.new(), disj_set}, fn x, {nodes, set} ->
59-
{parent, new_set} = DisjointSet.find(set, x)
60-
{MapSet.put(nodes, parent), new_set}
61-
end)
62-
|> elem(0)
27+
defp run_2(graph) do
28+
# Number of connected components in the graph
29+
graph
30+
|> Connectivity.connected_components()
31+
|> Enum.count()
6332
end
6433

6534
def parse(data \\ input()) do
6635
data
6736
|> Transformers.lines()
68-
|> Map.new(fn line ->
37+
|> Enum.reduce(Yog.undirected(), fn line, graph ->
6938
[node, connections] = line |> String.split(" <-> ")
70-
{String.to_integer(node), Transformers.int_words(connections, ", ")}
39+
u = String.to_integer(node)
40+
dests = Transformers.int_words(connections, ", ")
41+
42+
Enum.reduce(dests, graph, fn v, acc ->
43+
Yog.add_edge_ensure(acc, u, v, 1, nil)
44+
end)
7145
end)
7246
end
7347
end

lib/2022/day_24.ex

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,122 @@ defmodule AdventOfCode.Y2022.Day24 do
33
--- Day 24: Blizzard Basin ---
44
Problem Link: https://adventofcode.com/2022/day/24
55
Difficulty: m
6-
Tags: erlang slow optimization grid search
6+
Tags: graph implicit-graph shortest-path a-star
77
"""
88
alias AdventOfCode.Helpers.InputReader
9+
alias Yog.Pathfinding.AStar
910

1011
def input, do: InputReader.read_from_file(2022, 24)
1112

1213
def run(input \\ input()) do
13-
:day_22_24.solve(input)
14+
{up, down, left, right, w, h} = parse(input)
15+
16+
inner_w = w - 2
17+
inner_h = h - 2
18+
# LCM of dimensions for blizzard cycle
19+
# For AoC 120x25 or similar, LCM is small enough
20+
cycle = lcm(inner_w, inner_h)
21+
22+
start_pos = {1, 0}
23+
goal_pos = {inner_w, h - 1}
24+
25+
# Part 1: Start to Goal
26+
{:ok, t1} = solve_path(start_pos, goal_pos, 0, up, down, left, right, w, h, cycle)
27+
28+
# Part 2: Back to Start, then back to Goal
29+
{:ok, t2} = solve_path(goal_pos, start_pos, t1, up, down, left, right, w, h, cycle)
30+
{:ok, t3} = solve_path(start_pos, goal_pos, t2, up, down, left, right, w, h, cycle)
31+
32+
{t1, t3}
33+
end
34+
35+
defp solve_path({sx, sy}, {gx, gy}, start_t, up, down, left, right, w, h, cycle) do
36+
inner_w = w - 2
37+
inner_h = h - 2
38+
39+
is_valid = fn x, y, t ->
40+
cond do
41+
{x, y} == {sx, sy} -> true
42+
{x, y} == {gx, gy} -> true
43+
x <= 0 or x > inner_w or y <= 0 or y > inner_h -> false
44+
true -> not has_blizzard?(x, y, t, up, down, left, right, inner_w, inner_h, w)
45+
end
46+
end
47+
48+
successors = fn {x, y, t} ->
49+
nt = t + 1
50+
51+
[{x, y}, {x, y - 1}, {x, y + 1}, {x - 1, y}, {x + 1, y}]
52+
|> Enum.filter(fn {nx, ny} -> is_valid.(nx, ny, nt) end)
53+
|> Enum.map(fn {nx, ny} -> {{nx, ny, nt}, 1} end)
54+
end
55+
56+
visited_by = fn {x, y, t} -> {x, y, rem(t, cycle)} end
57+
is_goal = fn {x, y, _t} -> {x, y} == {gx, gy} end
58+
heuristic = fn {x, y, _t} -> abs(gx - x) + abs(gy - y) end
59+
60+
AStar.implicit_a_star_by(
61+
from: {sx, sy, start_t},
62+
successors_with_cost: successors,
63+
visited_by: visited_by,
64+
is_goal: is_goal,
65+
heuristic: heuristic
66+
)
67+
|> case do
68+
{:ok, cost} -> {:ok, start_t + cost}
69+
:error -> :error
70+
end
71+
end
72+
73+
defp has_blizzard?(x, y, t, up, down, left, right, iw, ih, w) do
74+
# At time t, a tile (x, y) is occupied if:
75+
# An '^' blizzard was at (x, (y-1+t) % ih + 1)
76+
# A 'v' blizzard was at (x, (y-1-t) % ih + 1)
77+
# A '<' blizzard was at ((x-1+t) % iw + 1, y)
78+
# A '>' blizzard was at ((x-1-t) % iw + 1, y)
79+
80+
uy = rem_euclid(y - 1 + t, ih) + 1
81+
dy = rem_euclid(y - 1 - t, ih) + 1
82+
lx = rem_euclid(x - 1 + t, iw) + 1
83+
rx = rem_euclid(x - 1 - t, iw) + 1
84+
85+
MapSet.member?(up, uy * w + x) or
86+
MapSet.member?(down, dy * w + x) or
87+
MapSet.member?(left, y * w + lx) or
88+
MapSet.member?(right, y * w + rx)
89+
end
90+
91+
defp parse(input) do
92+
lines = input |> String.trim() |> String.split("\n")
93+
h = length(lines)
94+
w = String.length(hd(lines))
95+
96+
points =
97+
for {line, y} <- Enum.with_index(lines),
98+
{char, x} <- Enum.with_index(String.graphemes(line)),
99+
char in ["^", "v", "<", ">"] do
100+
{y * w + x, char}
101+
end
102+
103+
up = filter_points(points, "^")
104+
down = filter_points(points, "v")
105+
left = filter_points(points, "<")
106+
right = filter_points(points, ">")
107+
108+
{up, down, left, right, w, h}
109+
end
110+
111+
defp filter_points(points, char) do
112+
points
113+
|> Enum.filter(fn {_, c} -> c == char end)
114+
|> Enum.map(fn {pos, _} -> pos end)
115+
|> MapSet.new()
116+
end
117+
118+
defp lcm(a, b), do: div(abs(a * b), Integer.gcd(a, b))
119+
120+
defp rem_euclid(a, b) do
121+
r = rem(a, b)
122+
if r < 0, do: r + b, else: r
14123
end
15124
end

lib/2024/day_05.ex

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,91 @@ defmodule AdventOfCode.Y2024.Day05 do
33
--- Day 5: Print Queue ---
44
Problem Link: https://adventofcode.com/2024/day/5
55
Difficulty: xs
6-
Tags: set sort
6+
Tags: graph topological-sort sort
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
alias Yog.Traversal
910

1011
def input, do: InputReader.read_from_file(2024, 5)
1112

1213
def run(input \\ input()) do
13-
input = parse(input)
14+
{rules, updates} = parse(input)
1415

15-
{run_1(input), run_2(input)}
16+
# Process all updates based on the rules.
17+
# For each update, we produce a correctly ordered version using topological sort.
18+
processed =
19+
for update <- updates do
20+
reordered = reorder(update, rules)
21+
{update, reordered}
22+
end
23+
24+
{run_1(processed), run_2(processed)}
25+
end
26+
27+
defp run_1(processed) do
28+
processed
29+
|> Enum.filter(fn {original, reordered} -> original == reordered end)
30+
|> Enum.sum_by(fn {reordered, _} -> get_middle(reordered) end)
1631
end
1732

18-
defp run_1(input) do
19-
input
20-
|> Enum.filter(fn {a, b} -> a == b end)
21-
|> Enum.sum_by(fn {a, _} -> a |> Enum.at(div(length(a), 2)) end)
33+
defp run_2(processed) do
34+
processed
35+
|> Enum.filter(fn {original, reordered} -> original != reordered end)
36+
|> Enum.sum_by(fn {_, reordered} -> get_middle(reordered) end)
2237
end
2338

24-
defp run_2(input) do
25-
input
26-
|> Enum.filter(fn {a, b} -> a != b end)
27-
|> Enum.sum_by(fn {_, b} -> b |> Enum.at(div(length(b), 2)) end)
39+
defp get_middle(list) do
40+
Enum.at(list, div(length(list), 2))
2841
end
2942

3043
def parse(data \\ input()) do
31-
[deps, updates] = Transformers.sections(data)
32-
given_sorted_pair({parse_deps(deps), parse_updates(updates)})
44+
[rules_raw, updates_raw] = Transformers.sections(data)
45+
{parse_rules(rules_raw), parse_updates(updates_raw)}
3346
end
3447

35-
defp parse_deps(deps) do
36-
for line <- Transformers.lines(deps),
37-
into: MapSet.new(),
38-
do: String.split(line, "|") |> Enum.map(&String.to_integer/1) |> List.to_tuple()
48+
defp parse_rules(raw) do
49+
for line <- Transformers.lines(raw) do
50+
[a, b] = String.split(line, "|") |> Enum.map(&String.to_integer/1)
51+
{a, b}
52+
end
3953
end
4054

41-
defp parse_updates(updates) do
42-
for line <- Transformers.lines(updates) do
43-
for update <- String.split(line, ","), do: String.to_integer(update)
55+
defp parse_updates(raw) do
56+
for line <- Transformers.lines(raw) do
57+
line |> String.split(",") |> Enum.map(&String.to_integer/1)
4458
end
4559
end
4660

47-
defp given_sorted_pair({deps, updates}) do
48-
for update <- updates do
49-
{update, Enum.sort(update, &({&1, &2} in deps))}
61+
# Reorder an update by building a dependency graph and performing a topological sort.
62+
defp reorder(update, rules) do
63+
update_set = MapSet.new(update)
64+
65+
# Filter rules that only apply to the current set of pages.
66+
active_rules =
67+
Enum.filter(rules, fn {a, b} ->
68+
MapSet.member?(update_set, a) and MapSet.member?(update_set, b)
69+
end)
70+
71+
# Build a directed graph of dependencies.
72+
graph =
73+
Enum.reduce(active_rules, Yog.directed(), fn {a, b}, acc ->
74+
Yog.add_edge_ensure(acc, a, b, 1, nil)
75+
end)
76+
77+
# Ensure all pages in the update are represented in the graph.
78+
graph =
79+
Enum.reduce(update, graph, fn page, acc ->
80+
if Yog.has_node?(acc, page) do
81+
acc
82+
else
83+
Yog.add_node(acc, page, nil)
84+
end
85+
end)
86+
87+
# Correct sequence is the topological order of these dependencies.
88+
case Traversal.topological_sort(graph) do
89+
{:ok, sorted} -> sorted
90+
{:error, :contains_cycle} -> update
5091
end
5192
end
5293
end

0 commit comments

Comments
 (0)