@@ -3,76 +3,128 @@ defmodule AdventOfCode.Y2016.Day12 do
33 --- Day 12: Leonardo's Monorail ---
44 Problem Link: https://adventofcode.com/2016/day/12
55 Difficulty: s
6- Tags: slow op-code
6+ Tags: assembled-optimization assembunny
77 """
88 alias AdventOfCode.Helpers . { InputReader , Transformers }
99
1010 def input , do: InputReader . read_from_file ( 2016 , 12 )
1111
12- @ tokens ~w/ a b c d cpy inc dec jnz/
13- @ regs % { "a" => 0 , "b" => 0 , "c" => 0 , "d" => 0 }
14-
1512 def run ( input \\ input ( ) ) do
1613 instructions = parse ( input )
14+ instr_tuple = List . to_tuple ( instructions )
15+ size = tuple_size ( instr_tuple )
1716
18- solution_1 = Task . async ( fn -> run_1 ( instructions ) end )
19- solution_2 = Task . async ( fn -> run_2 ( instructions ) end )
20-
21- {
22- Task . await ( solution_1 , :infinity ) ,
23- Task . await ( solution_2 , :infinity )
24- }
25- end
26-
27- def run_1 ( instructions ) do
28- Map . get ( exec ( instructions , @ regs , 0 , Enum . count ( instructions ) ) , "a" )
29- end
17+ task_1 = Task . async ( fn -> solve ( instr_tuple , { 0 , 0 , 0 , 0 } , 0 , size ) end )
18+ task_2 = Task . async ( fn -> solve ( instr_tuple , { 0 , 0 , 1 , 0 } , 0 , size ) end )
3019
31- def run_2 ( instructions ) do
32- Map . get ( exec ( instructions , % { @ regs | "c" => 1 } , 0 , Enum . count ( instructions ) ) , "a" )
20+ { Task . await ( task_1 , :infinity ) , Task . await ( task_2 , :infinity ) }
3321 end
3422
3523 def parse ( data ) do
3624 data
3725 |> Transformers . lines ( )
38- |> Enum . map ( fn instruction ->
39- instruction
40- |> String . split ( " " )
41- |> Enum . map ( & sanitize / 1 )
26+ |> Enum . map ( fn line ->
27+ String . split ( line , " " ) |> Enum . map ( & sanitize / 1 )
4228 end )
43- |> Enum . with_index ( )
44- |> Map . new ( fn { cmd , idx } -> { idx , cmd } end )
4529 end
4630
47- defp exec ( _ , regs , s , s ) , do: regs
31+ # Use atoms for registers to distinguish from constant integers
32+ defp sanitize ( val ) do
33+ case val do
34+ "a" ->
35+ :a
4836
49- defp exec ( cmds , regs , idx , s ) do
50- case cmds [ idx ] do
51- [ "cpy" , val , reg ] when is_integer ( val ) ->
52- exec ( cmds , % { regs | reg => val } , idx + 1 , s )
37+ "b" ->
38+ :b
5339
54- [ "cpy" , reg_val , reg ] ->
55- exec ( cmds , % { regs | reg => regs [ reg_val ] } , idx + 1 , s )
40+ "c" ->
41+ :c
5642
57- [ "inc" , reg ] ->
58- exec ( cmds , % { regs | reg => regs [ reg ] + 1 } , idx + 1 , s )
43+ "d" ->
44+ :d
5945
60- [ "dec" , reg ] ->
61- exec ( cmds , % { regs | reg => regs [ reg ] - 1 } , idx + 1 , s )
62-
63- [ "jnz" , 0 , _ ] ->
64- exec ( cmds , regs , idx + 1 , s )
65-
66- [ "jnz" , val , step ] when is_integer ( val ) ->
67- exec ( cmds , regs , idx + step , s )
68-
69- [ "jnz" , reg_val , step ] ->
70- exec ( cmds , regs , jump_on_nz ( regs [ reg_val ] , idx , step ) , s )
46+ other ->
47+ case Integer . parse ( other ) do
48+ { n , "" } -> n
49+ _ -> other
50+ end
7151 end
7252 end
7353
74- defp jump_on_nz ( val , idx , step ) , do: idx + ( ( val == 0 && 1 ) || step )
54+ defp solve ( _ , { a , _ , _ , _ } , pc , size ) when pc >= size , do: a
55+
56+ defp solve ( cmds , regs , pc , size ) do
57+ case elem ( cmds , pc ) do
58+ [ "inc" , r1 ] ->
59+ # Loop Optimization: Add R2 to R1 and zero out R2
60+ if pc + 2 < size do
61+ case elem ( cmds , pc + 1 ) do
62+ [ "dec" , r2 ] ->
63+ case elem ( cmds , pc + 2 ) do
64+ [ "jnz" , ^ r2 , - 2 ] ->
65+ val = get_reg ( regs , r1 ) + get_reg ( regs , r2 )
66+ solve ( cmds , put_reg ( regs , r1 , val ) |> put_reg ( r2 , 0 ) , pc + 3 , size )
67+
68+ _ ->
69+ val = get_reg ( regs , r1 ) + 1
70+ solve ( cmds , put_reg ( regs , r1 , val ) , pc + 1 , size )
71+ end
72+
73+ _ ->
74+ val = get_reg ( regs , r1 ) + 1
75+ solve ( cmds , put_reg ( regs , r1 , val ) , pc + 1 , size )
76+ end
77+ else
78+ val = get_reg ( regs , r1 ) + 1
79+ solve ( cmds , put_reg ( regs , r1 , val ) , pc + 1 , size )
80+ end
81+
82+ [ "dec" , r1 ] ->
83+ # Loop Optimization: dec r1; inc r2; jnz r1 -2
84+ if pc + 2 < size do
85+ case elem ( cmds , pc + 1 ) do
86+ [ "inc" , r2 ] ->
87+ case elem ( cmds , pc + 2 ) do
88+ [ "jnz" , ^ r1 , - 2 ] ->
89+ val = get_reg ( regs , r2 ) + get_reg ( regs , r1 )
90+ solve ( cmds , put_reg ( regs , r2 , val ) |> put_reg ( r1 , 0 ) , pc + 3 , size )
91+
92+ _ ->
93+ val = get_reg ( regs , r1 ) - 1
94+ solve ( cmds , put_reg ( regs , r1 , val ) , pc + 1 , size )
95+ end
96+
97+ _ ->
98+ val = get_reg ( regs , r1 ) - 1
99+ solve ( cmds , put_reg ( regs , r1 , val ) , pc + 1 , size )
100+ end
101+ else
102+ val = get_reg ( regs , r1 ) - 1
103+ solve ( cmds , put_reg ( regs , r1 , val ) , pc + 1 , size )
104+ end
105+
106+ [ "cpy" , src , target ] ->
107+ val = get_reg ( regs , src )
108+ solve ( cmds , put_reg ( regs , target , val ) , pc + 1 , size )
109+
110+ [ "jnz" , src , step ] ->
111+ val = get_reg ( regs , src )
112+ new_pc = if val != 0 , do: pc + step , else: pc + 1
113+ solve ( cmds , regs , new_pc , size )
114+ end
115+ end
75116
76- defp sanitize ( reg ) when reg in @ tokens , do: reg
77- defp sanitize ( reg ) , do: String . to_integer ( reg )
117+ # Helper functions for O(1) register access using pattern matching
118+ @ compile { :inline , get_reg: 2 , put_reg: 3 }
119+ defp get_reg ( { a , _ , _ , _ } , :a ) , do: a
120+ defp get_reg ( { _ , b , _ , _ } , :b ) , do: b
121+ defp get_reg ( { _ , _ , c , _ } , :c ) , do: c
122+ defp get_reg ( { _ , _ , _ , d } , :d ) , do: d
123+ # It's a constant integer
124+ defp get_reg ( _ , val ) , do: val
125+
126+ defp put_reg ( { _ , b , c , d } , :a , v ) , do: { v , b , c , d }
127+ defp put_reg ( { a , _ , c , d } , :b , v ) , do: { a , v , c , d }
128+ defp put_reg ( { a , b , _ , d } , :c , v ) , do: { a , b , v , d }
129+ defp put_reg ( { a , b , c , _ } , :d , v ) , do: { a , b , c , v }
78130end
0 commit comments