Skip to content

Commit 51a83eb

Browse files
committed
Solve 2020/23.2
1 parent 1d8cd99 commit 51a83eb

1 file changed

Lines changed: 90 additions & 30 deletions

File tree

lib/2020/day_23.ex

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,111 @@ defmodule AdventOfCode.Y2020.Day23 do
33
--- Day 23: Crab Cups ---
44
Problem Link: https://adventofcode.com/2020/day/23
55
Difficulty: l
6-
Tags: linked-list circular-linked-list half-done
6+
Tags: circular-linked-list slow
77
"""
88
def input, do: "467528193"
9-
@part_1_limit 100
109

11-
def run_1, do: parse() |> play() |> labels_after(1)
12-
def run_2, do: {:not_implemented, 2}
13-
def parse, do: Enum.map(String.graphemes(input()), &String.to_integer/1)
10+
def run(input \\ input()) do
11+
cups = parse(input)
1412

15-
def play(cups), do: play(cups, hd(cups), 1)
16-
def play(cups, _, @part_1_limit), do: cups
13+
{
14+
solve(cups, 100, 9),
15+
solve(cups, 10_000_000, 1_000_000)
16+
}
17+
end
1718

18-
def play(cups, current, move) do
19-
pick_ups = pick_up(cups, current)
20-
destination = destination(cups, pick_ups, current - 1)
19+
def parse(input \\ input()) do
20+
input |> String.graphemes() |> Enum.map(&String.to_integer/1)
21+
end
2122

22-
{init, [head | tail]} = Enum.split_while(cups -- pick_ups, &(&1 != destination))
23-
new_cups = init ++ [head | pick_ups] ++ tail
23+
# Part 1 and Part 2 use the same logic, but different constraints
24+
defp solve(initial_cups, moves, total_cups) do
25+
# 1. Build the circular linked list using :counters (O(1) mutable array)
26+
arr = :counters.new(total_cups + 1, [:write_concurrency])
2427

25-
play(new_cups, next(new_cups, current), move + 1)
26-
end
28+
# Fill initial cups
29+
initial_len = length(initial_cups)
30+
max_initial = Enum.max(initial_cups)
2731

28-
def pick_up(cups, current) do
29-
{a, [_ | b]} = Enum.split_while(cups, &(&1 != current))
30-
Enum.take(b ++ a, 3)
31-
end
32+
initial_cups
33+
|> Enum.chunk_every(2, 1, [])
34+
|> Enum.each(fn
35+
[curr, nxt] ->
36+
:counters.put(arr, curr, nxt)
37+
38+
[last] ->
39+
if total_cups > initial_len do
40+
:counters.put(arr, last, max_initial + 1)
41+
else
42+
:counters.put(arr, last, hd(initial_cups))
43+
end
44+
end)
45+
46+
# Fill the rest for Part 2
47+
if total_cups > initial_len do
48+
Enum.each((max_initial + 1)..total_cups, fn i ->
49+
if i == total_cups do
50+
:counters.put(arr, i, hd(initial_cups))
51+
else
52+
:counters.put(arr, i, i + 1)
53+
end
54+
end)
55+
end
56+
57+
first_cup = hd(initial_cups)
58+
59+
play(arr, first_cup, moves, total_cups)
3260

33-
def next(cups, current) do
34-
case Enum.split_while(cups, &(&1 != current)) do
35-
{[next | _], [_]} -> next
36-
{_, [_ | [next | _]]} -> next
61+
if total_cups == 9 do
62+
# Part 1: Labels after cup 1
63+
read_after(arr, 1, 8)
64+
else
65+
# Part 2: Product of two cups after cup 1
66+
c1 = :counters.get(arr, 1)
67+
c2 = :counters.get(arr, c1)
68+
c1 * c2
3769
end
3870
end
3971

40-
def destination(cups, pick_ups, from) do
41-
if from in pick_ups do
42-
destination(cups, pick_ups, from - 1)
72+
defp play(_arr, _current, 0, _), do: :ok
73+
74+
defp play(arr, current, moves, max) do
75+
# 1. Pick up three cups immediately clockwise of current
76+
c1 = :counters.get(arr, current)
77+
c2 = :counters.get(arr, c1)
78+
c3 = :counters.get(arr, c2)
79+
after_picked = :counters.get(arr, c3)
80+
81+
# 2. Select destination cup
82+
dest = select_destination(current - 1, c1, c2, c3, max)
83+
84+
# 3. Update pointers to "insert" cups
85+
after_dest = :counters.get(arr, dest)
86+
87+
:counters.put(arr, current, after_picked)
88+
:counters.put(arr, dest, c1)
89+
:counters.put(arr, c3, after_dest)
90+
91+
play(arr, after_picked, moves - 1, max)
92+
end
93+
94+
defp select_destination(0, c1, c2, c3, max), do: select_destination(max, c1, c2, c3, max)
95+
96+
defp select_destination(dest, c1, c2, c3, max) do
97+
if dest == c1 or dest == c2 or dest == c3 do
98+
select_destination(dest - 1, c1, c2, c3, max)
4399
else
44-
{min, max} = Enum.min_max(cups)
45-
(from < min && destination(cups, pick_ups, max)) || from
100+
dest
46101
end
47102
end
48103

49-
def labels_after(cups, label) do
50-
{a, [_ | b]} = Enum.split_while(cups, &(&1 != label))
51-
Enum.join(b ++ a)
104+
defp read_after(arr, start, count) do
105+
Enum.reduce(1..count, {[], start}, fn _, {acc, curr} ->
106+
nxt = :counters.get(arr, curr)
107+
{[nxt | acc], nxt}
108+
end)
109+
|> elem(0)
110+
|> Enum.reverse()
111+
|> Enum.join()
52112
end
53113
end

0 commit comments

Comments
 (0)