@@ -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
53113end
0 commit comments