Skip to content

Commit f9ab56d

Browse files
committed
Complete 2021
1 parent 97ead1e commit f9ab56d

18 files changed

Lines changed: 590 additions & 10 deletions

README.md

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

lib/2023/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
[Home](../../README.md) | [2015](../2015/README.md) | [2016](../2016/README.md) | [2017](../2017/README.md) | [2018](../2018/README.md) | [2019](../2019/README.md) | [2020](../2020/README.md) | [2021](../2021/README.md) | [2022](../2022/README.md) | 2023 | [2024](../2024/README.md) | [2025](../2025/README.md)
44

5-
## 46/50
5+
## 50/50
66

7-
[grid](../../wiki/tags/grid.md) `6` [graph](../../wiki/tags/graph.md) `4` [memoization](../../wiki/tags/memoization.md) `3` [reduction](../../wiki/tags/reduction.md) `3` [geometry2d](../../wiki/tags/geometry2d.md) `2` [range](../../wiki/tags/range.md) `2` [sequence](../../wiki/tags/sequence.md) `2` [algebra](../../wiki/tags/algebra.md) `1` [arithmetic](../../wiki/tags/arithmetic.md) `1` [bfs](../../wiki/tags/bfs.md) `1` [dijkstra](../../wiki/tags/dijkstra.md) `1` [gb-tree](../../wiki/tags/gb-tree.md) `1` [hash](../../wiki/tags/hash.md) `1` [longest-path](../../wiki/tags/longest-path.md) `1` [map](../../wiki/tags/map.md) `1` [measurement](../../wiki/tags/measurement.md) `1` [min-cut](../../wiki/tags/min-cut.md) `1` [modular-arithmetic](../../wiki/tags/modular-arithmetic.md) `1` [op-code](../../wiki/tags/op-code.md) `1` [ordered-list](../../wiki/tags/ordered-list.md) `1` [palindrome](../../wiki/tags/palindrome.md) `1` [parse-heavy](../../wiki/tags/parse-heavy.md) `1` [pattern-matching](../../wiki/tags/pattern-matching.md) `1` [refactor](../../wiki/tags/refactor.md) `1` [regex](../../wiki/tags/regex.md) `1` [rotation](../../wiki/tags/rotation.md) `1` [rust](../../wiki/tags/rust.md) `1` [set](../../wiki/tags/set.md) `1` [shortest-path](../../wiki/tags/shortest-path.md) `1` [sliding-window](../../wiki/tags/sliding-window.md) `1` [stack](../../wiki/tags/stack.md) `1` [state-space](../../wiki/tags/state-space.md) `1`
7+
[graph](../../wiki/tags/graph.md) `6` [grid](../../wiki/tags/grid.md) `6` [memoization](../../wiki/tags/memoization.md) `3` [reduction](../../wiki/tags/reduction.md) `3` [bfs](../../wiki/tags/bfs.md) `2` [cycles](../../wiki/tags/cycles.md) `2` [geometry2d](../../wiki/tags/geometry2d.md) `2` [range](../../wiki/tags/range.md) `2` [sequence](../../wiki/tags/sequence.md) `2` [algebra](../../wiki/tags/algebra.md) `1` [arithmetic](../../wiki/tags/arithmetic.md) `1` [dijkstra](../../wiki/tags/dijkstra.md) `1` [gb-tree](../../wiki/tags/gb-tree.md) `1` [hash](../../wiki/tags/hash.md) `1` [infinite-grid](../../wiki/tags/infinite-grid.md) `1` [lcm](../../wiki/tags/lcm.md) `1` [logic-circuit](../../wiki/tags/logic-circuit.md) `1` [longest-path](../../wiki/tags/longest-path.md) `1` [map](../../wiki/tags/map.md) `1` [measurement](../../wiki/tags/measurement.md) `1` [min-cut](../../wiki/tags/min-cut.md) `1` [modular-arithmetic](../../wiki/tags/modular-arithmetic.md) `1` [op-code](../../wiki/tags/op-code.md) `1` [ordered-list](../../wiki/tags/ordered-list.md) `1` [palindrome](../../wiki/tags/palindrome.md) `1` [parse-heavy](../../wiki/tags/parse-heavy.md) `1` [pattern-matching](../../wiki/tags/pattern-matching.md) `1` [quadratic-interpolation](../../wiki/tags/quadratic-interpolation.md) `1` [refactor](../../wiki/tags/refactor.md) `1` [regex](../../wiki/tags/regex.md) `1` [rotation](../../wiki/tags/rotation.md) `1` [rust](../../wiki/tags/rust.md) `1` [set](../../wiki/tags/set.md) `1` [shortest-path](../../wiki/tags/shortest-path.md) `1` [simulation](../../wiki/tags/simulation.md) `1` [sliding-window](../../wiki/tags/sliding-window.md) `1` [stack](../../wiki/tags/stack.md) `1` [state-space](../../wiki/tags/state-space.md) `1`
88

99
| Day | Title | Difficulty | Tags | Source |
1010
|:---:|-------|:----------:|------|--------|
@@ -27,6 +27,8 @@
2727
| [17](https://adventofcode.com/2023/day/17) | [Clumsy Crucible](https://adventofcode.com/2023/day/17) | 🔴 | [graph](../../wiki/tags/graph.md), [dijkstra](../../wiki/tags/dijkstra.md), [shortest-path](../../wiki/tags/shortest-path.md), [state-space](../../wiki/tags/state-space.md), [grid](../../wiki/tags/grid.md) | [day_17.ex](day_17.ex) |
2828
| [18](https://adventofcode.com/2023/day/18) | [Lavaduct Lagoon](https://adventofcode.com/2023/day/18) | 🔴 | [geometry2d](../../wiki/tags/geometry2d.md) | [day_18.ex](day_18.ex) |
2929
| [19](https://adventofcode.com/2023/day/19) | [Aplenty](https://adventofcode.com/2023/day/19) | 🔴 | [op-code](../../wiki/tags/op-code.md), [parse-heavy](../../wiki/tags/parse-heavy.md) | [day_19.ex](day_19.ex) |
30+
| [20](https://adventofcode.com/2023/day/20) | [Pulse Propagation](https://adventofcode.com/2023/day/20) | 🟠 | [simulation](../../wiki/tags/simulation.md), [logic-circuit](../../wiki/tags/logic-circuit.md), [graph](../../wiki/tags/graph.md), [cycles](../../wiki/tags/cycles.md), [lcm](../../wiki/tags/lcm.md) | [day_20.ex](day_20.ex) |
31+
| [21](https://adventofcode.com/2023/day/21) | [Step Counter](https://adventofcode.com/2023/day/21) | 🟠 | [graph](../../wiki/tags/graph.md), [infinite-grid](../../wiki/tags/infinite-grid.md), [bfs](../../wiki/tags/bfs.md), [cycles](../../wiki/tags/cycles.md), [quadratic-interpolation](../../wiki/tags/quadratic-interpolation.md) | [day_21.ex](day_21.ex) |
3032
| [22](https://adventofcode.com/2023/day/22) | [Sand Slabs](https://adventofcode.com/2023/day/22) | 💀 | [stack](../../wiki/tags/stack.md), [range](../../wiki/tags/range.md) | [day_22.ex](day_22.ex) |
3133
| [23](https://adventofcode.com/2023/day/23) | [A Long Walk](https://adventofcode.com/2023/day/23) | 💀 | [graph](../../wiki/tags/graph.md), [longest-path](../../wiki/tags/longest-path.md) | [day_23.ex](day_23.ex) |
3234
| [24](https://adventofcode.com/2023/day/24) | [Never Tell Me The Odds](https://adventofcode.com/2023/day/24) | 💀 | [geometry2d](../../wiki/tags/geometry2d.md), [refactor](../../wiki/tags/refactor.md) | [day_24.ex](day_24.ex) |

lib/2023/day_20.ex

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
defmodule AdventOfCode.Y2023.Day20 do
2+
@moduledoc """
3+
--- Day 20: Pulse Propagation ---
4+
Problem Link: https://adventofcode.com/2023/day/20
5+
Difficulty: h
6+
Tags: simulation logic-circuit graph cycles lcm
7+
"""
8+
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
10+
def input, do: InputReader.read_from_file(2023, 20)
11+
12+
def run(input \\ input()) do
13+
{modules, graph} = parse_modules(input)
14+
15+
{run_1(modules, graph), run_2(modules, graph)}
16+
end
17+
18+
defp run_1(modules, graph) do
19+
state = init_state(modules, graph)
20+
21+
{_final_state, {lows, highs}} =
22+
Enum.reduce(1..1000, {state, {0, 0}}, fn _, {curr_state, {l, h}} ->
23+
{next_state, {new_l, new_h}} =
24+
send_pulse(curr_state, modules, {"button", "broadcaster", :low})
25+
26+
{next_state, {l + new_l, h + new_h}}
27+
end)
28+
29+
lows * highs
30+
end
31+
32+
defp run_2(modules, graph) do
33+
hb = graph |> Map.get("rx") |> hd()
34+
ancestors = Map.get(graph, hb, [])
35+
36+
state = init_state(modules, graph)
37+
38+
periods =
39+
Enum.reduce_while(Stream.iterate(1, &(&1 + 1)), {state, %{}}, fn press,
40+
{curr_state, found} ->
41+
{next_state, high_events} =
42+
send_pulse_with_tracking(curr_state, modules, {"button", "broadcaster", :low})
43+
44+
new_found =
45+
Enum.reduce(high_events, found, fn {from, _to, pulse}, acc ->
46+
if pulse == :high and from in ancestors and !Map.has_key?(acc, from) do
47+
Map.put(acc, from, press)
48+
else
49+
acc
50+
end
51+
end)
52+
53+
if map_size(new_found) == length(ancestors) do
54+
{:halt, new_found}
55+
else
56+
{:cont, {next_state, new_found}}
57+
end
58+
end)
59+
60+
periods |> Map.values() |> Enum.reduce(1, &lcm/2)
61+
end
62+
63+
defp send_pulse(state, modules, initial_pulse) do
64+
q = :queue.from_list([initial_pulse])
65+
process_pulses(q, state, modules, {0, 0})
66+
end
67+
68+
defp process_pulses(q, state, modules, {l, h}) do
69+
case :queue.out(q) do
70+
{:empty, _} ->
71+
{state, {l, h}}
72+
73+
{{:value, {from, to, pulse}}, rest} ->
74+
{new_l, new_h} = if pulse == :low, do: {l + 1, h}, else: {l, h + 1}
75+
76+
case Map.get(modules, to) do
77+
nil ->
78+
process_pulses(rest, state, modules, {new_l, new_h})
79+
80+
%{type: :broadcaster, dests: dests} ->
81+
new_pulses = Enum.map(dests, fn d -> {to, d, pulse} end)
82+
process_pulses(enqueue_list(rest, new_pulses), state, modules, {new_l, new_h})
83+
84+
%{type: :flip_flop, dests: dests} ->
85+
if pulse == :high do
86+
process_pulses(rest, state, modules, {new_l, new_h})
87+
else
88+
curr_on = Map.get(state, to)
89+
new_state = Map.put(state, to, !curr_on)
90+
new_pulse = if curr_on, do: :low, else: :high
91+
new_pulses = Enum.map(dests, fn d -> {to, d, new_pulse} end)
92+
process_pulses(enqueue_list(rest, new_pulses), new_state, modules, {new_l, new_h})
93+
end
94+
95+
%{type: :conjunction, dests: dests} ->
96+
conj_state = Map.get(state, to) |> Map.put(from, pulse)
97+
new_state = Map.put(state, to, conj_state)
98+
99+
output_pulse =
100+
if Enum.all?(conj_state, fn {_, p} -> p == :high end), do: :low, else: :high
101+
102+
new_pulses = Enum.map(dests, fn d -> {to, d, output_pulse} end)
103+
process_pulses(enqueue_list(rest, new_pulses), new_state, modules, {new_l, new_h})
104+
end
105+
end
106+
end
107+
108+
defp send_pulse_with_tracking(state, modules, initial_pulse) do
109+
q = :queue.from_list([initial_pulse])
110+
process_pulses_tracking(q, state, modules, [])
111+
end
112+
113+
defp process_pulses_tracking(q, state, modules, events) do
114+
case :queue.out(q) do
115+
{:empty, _} ->
116+
{state, Enum.reverse(events)}
117+
118+
{{:value, {from, to, pulse}}, rest} ->
119+
new_events = [{from, to, pulse} | events]
120+
121+
case Map.get(modules, to) do
122+
nil ->
123+
process_pulses_tracking(rest, state, modules, new_events)
124+
125+
%{type: :broadcaster, dests: dests} ->
126+
new_pulses = Enum.map(dests, fn d -> {to, d, pulse} end)
127+
process_pulses_tracking(enqueue_list(rest, new_pulses), state, modules, new_events)
128+
129+
%{type: :flip_flop, dests: dests} ->
130+
if pulse == :high do
131+
process_pulses_tracking(rest, state, modules, new_events)
132+
else
133+
curr_on = Map.get(state, to)
134+
new_state = Map.put(state, to, !curr_on)
135+
new_pulse = if curr_on, do: :low, else: :high
136+
new_pulses = Enum.map(dests, fn d -> {to, d, new_pulse} end)
137+
138+
process_pulses_tracking(
139+
enqueue_list(rest, new_pulses),
140+
new_state,
141+
modules,
142+
new_events
143+
)
144+
end
145+
146+
%{type: :conjunction, dests: dests} ->
147+
conj_state = Map.get(state, to) |> Map.put(from, pulse)
148+
new_state = Map.put(state, to, conj_state)
149+
150+
output_pulse =
151+
if Enum.all?(conj_state, fn {_, p} -> p == :high end), do: :low, else: :high
152+
153+
new_pulses = Enum.map(dests, fn d -> {to, d, output_pulse} end)
154+
155+
process_pulses_tracking(
156+
enqueue_list(rest, new_pulses),
157+
new_state,
158+
modules,
159+
new_events
160+
)
161+
end
162+
end
163+
end
164+
165+
defp enqueue_list(q, list) do
166+
Enum.reduce(list, q, fn item, acc -> :queue.in(item, acc) end)
167+
end
168+
169+
defp init_state(modules, graph) do
170+
Enum.reduce(modules, %{}, fn
171+
{name, %{type: :flip_flop}}, acc ->
172+
Map.put(acc, name, false)
173+
174+
{name, %{type: :conjunction}}, acc ->
175+
inputs = Map.get(graph, name, [])
176+
Map.put(acc, name, Map.new(inputs, &{&1, :low}))
177+
178+
_, acc ->
179+
acc
180+
end)
181+
end
182+
183+
defp parse_modules(data) do
184+
modules =
185+
data
186+
|> Transformers.lines()
187+
|> Enum.map(fn line ->
188+
[src_raw, dests_raw] = String.split(line, " -> ")
189+
dests = String.split(dests_raw, ", ")
190+
191+
{name, type} =
192+
cond do
193+
String.starts_with?(src_raw, "%") ->
194+
{String.replace_prefix(src_raw, "%", ""), :flip_flop}
195+
196+
String.starts_with?(src_raw, "&") ->
197+
{String.replace_prefix(src_raw, "&", ""), :conjunction}
198+
199+
src_raw == "broadcaster" ->
200+
{"broadcaster", :broadcaster}
201+
end
202+
203+
{name, %{type: type, dests: dests}}
204+
end)
205+
|> Map.new()
206+
207+
edges = for {name, %{dests: ds}} <- modules, d <- ds, do: {d, name}
208+
graph = Enum.group_by(edges, &elem(&1, 0), &elem(&1, 1))
209+
210+
{modules, graph}
211+
end
212+
213+
defp gcd(a, 0), do: a
214+
defp gcd(a, b), do: gcd(b, rem(a, b))
215+
defp lcm(a, b), do: div(abs(a * b), gcd(a, b))
216+
217+
def parse(data \\ input()) do
218+
data |> Transformers.lines()
219+
end
220+
end

lib/2023/day_21.ex

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
defmodule AdventOfCode.Y2023.Day21 do
2+
@moduledoc """
3+
--- Day 21: Step Counter ---
4+
Problem Link: https://adventofcode.com/2023/day/21
5+
Difficulty: h
6+
Tags: graph infinite-grid bfs cycles quadratic-interpolation
7+
"""
8+
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
alias Yog.Traversal
10+
11+
def input, do: InputReader.read_from_file(2023, 21)
12+
13+
@spec run(binary()) :: {non_neg_integer(), integer()}
14+
def run(input \\ input()) do
15+
{grid, start, size} = parse_grid(input)
16+
17+
{run_1(grid, start, 64), run_2(grid, start, size, 26_501_365)}
18+
end
19+
20+
defp run_1(grid, start, steps) do
21+
distances =
22+
Traversal.implicit_fold(
23+
from: start,
24+
using: :breadth_first,
25+
initial: %{start => 0},
26+
successors_of: fn {x, y} ->
27+
for {dx, dy} <- [{0, 1}, {0, -1}, {1, 0}, {-1, 0}],
28+
nxt = {x + dx, y + dy},
29+
Map.get(grid, nxt) == :garden,
30+
do: nxt
31+
end,
32+
with: fn acc, pos, meta ->
33+
new_acc = Map.put(acc, pos, meta.depth)
34+
if meta.depth < steps, do: {:continue, new_acc}, else: {:stop, new_acc}
35+
end
36+
)
37+
38+
distances
39+
|> Enum.count(fn {_, d} -> d <= steps and rem(d, 2) == rem(steps, 2) end)
40+
end
41+
42+
defp run_2(grid, start, size, target_steps) do
43+
n = div(target_steps, size)
44+
remainder = rem(target_steps, size)
45+
46+
f0 = count_reachable_infinite(grid, start, size, remainder)
47+
f1 = count_reachable_infinite(grid, start, size, remainder + size)
48+
f2 = count_reachable_infinite(grid, start, size, remainder + 2 * size)
49+
c = f0
50+
a = div(f2 - 2 * f1 + f0, 2)
51+
b = f1 - f0 - a
52+
53+
a * n * n + b * n + c
54+
end
55+
56+
defp count_reachable_infinite(grid, start, size, steps) do
57+
distances =
58+
Traversal.implicit_fold(
59+
from: start,
60+
using: :breadth_first,
61+
initial: %{start => 0},
62+
successors_of: fn {x, y} ->
63+
for {dx, dy} <- [{0, 1}, {0, -1}, {1, 0}, {-1, 0}],
64+
nx = x + dx,
65+
ny = y + dy,
66+
Map.get(grid, {Integer.mod(nx, size), Integer.mod(ny, size)}) == :garden,
67+
do: {nx, ny}
68+
end,
69+
with: fn acc, pos, meta ->
70+
new_acc = Map.put(acc, pos, meta.depth)
71+
if meta.depth < steps, do: {:continue, new_acc}, else: {:stop, new_acc}
72+
end
73+
)
74+
75+
parity = rem(steps, 2)
76+
77+
Enum.count(distances, fn {_, d} -> d <= steps and rem(d, 2) == parity end)
78+
end
79+
80+
defp parse_grid(input) do
81+
lines = Transformers.lines(input)
82+
size = length(lines)
83+
84+
grid =
85+
for {line, y} <- Enum.with_index(lines),
86+
{char, x} <- Enum.with_index(String.graphemes(line)),
87+
char != "#",
88+
into: %{} do
89+
{{x, y}, :garden}
90+
end
91+
92+
# Find S
93+
[start] =
94+
for {line, y} <- Enum.with_index(lines),
95+
{char, x} <- Enum.with_index(String.graphemes(line)),
96+
char == "S",
97+
do: {x, y}
98+
99+
{grid, start, size}
100+
end
101+
102+
def parse(data \\ input()) do
103+
data |> Transformers.lines()
104+
end
105+
end

0 commit comments

Comments
 (0)