Code for the Advent of Code event
aoc
advent-of-code
1#!/usr/bin/env ruby
2# frozen_string_literal: true
3
4class Pair
5 SIDE_INSPECT_S = { root: '', left: 'L', right: 'R' }.freeze
6
7 attr_accessor :parent, :left, :right, :side
8
9 def initialize(parent, left, right, side)
10 @parent = parent
11 @left = left
12 @right = right
13 @side = side
14 if @left.is_a?(Pair)
15 @left.parent = self
16 @left.side = :left
17 end
18 return unless @right.is_a?(Pair)
19 @right.parent = self
20 @right.side = :right
21 end
22
23 def self.from_array(array, parent = nil, side = :root)
24 pair = Pair.new(parent, nil, nil, side)
25
26 if array[0].is_a? Numeric # rubocop:disable Style/ConditionalAssignment
27 pair.left = array[0]
28 else
29 pair.left = from_array(array[0], pair, :left)
30 end
31
32 if array[1].is_a? Numeric # rubocop:disable Style/ConditionalAssignment
33 pair.right = array[1]
34 else
35 pair.right = from_array(array[1], pair, :right)
36 end
37
38 pair
39 end
40
41 def self.combine(left, right)
42 Pair.new(nil, left, right, :root)
43 end
44
45 def left?
46 @side == :left
47 end
48
49 def right?
50 @side == :right
51 end
52
53 def root?
54 @side == :root
55 end
56
57 def plain?
58 left.is_a?(Numeric) && right.is_a?(Numeric)
59 end
60
61 def depth
62 return 0 if parent.nil?
63 1 + parent.depth
64 end
65
66 def magnitude
67 left_mag = (left.is_a?(Pair) ? left.magnitude : left) * 3
68 right_mag = (right.is_a?(Pair) ? right.magnitude : right) * 2
69 left_mag + right_mag
70 end
71
72 def add_ancestor_left(value, came_from = nil, direction = :left)
73 if came_from.nil?
74 return if parent.nil?
75 parent.add_ancestor_left(value, self, direction)
76 elsif direction == :left
77 if @left.is_a?(Numeric)
78 @left += value
79 elsif @left == came_from
80 return if parent.nil?
81 parent.add_ancestor_left(value, self, direction)
82 else
83 @left.add_ancestor_left(value, self, :right)
84 end
85 elsif @right.is_a?(Numeric)
86 @right += value
87 else
88 @right.add_ancestor_left(value, self, :right)
89 end
90 end
91
92 def add_ancestor_right(value, came_from = nil, direction = :right)
93 if came_from.nil?
94 return if parent.nil?
95 parent.add_ancestor_right(value, self, direction)
96 elsif direction == :right
97 if @right.is_a?(Numeric)
98 @right += value
99 elsif @right == came_from
100 return if parent.nil?
101 parent.add_ancestor_right(value, self, direction)
102 else
103 @right.add_ancestor_right(value, self, :left)
104 end
105 elsif @left.is_a?(Numeric)
106 @left += value
107 else
108 @left.add_ancestor_right(value, self, :left)
109 end
110 end
111
112 def dup
113 p_dup(nil)
114 end
115 def p_dup(dup_p = nil)
116 Pair.new(dup_p, left.is_a?(Numeric) ? left : left.p_dup(self), right.is_a?(Numeric) ? right : right.p_dup(self), side)
117 end
118
119 def to_s
120 "[#{left}, #{right}]"
121 end
122
123 def inspect
124 "#{SIDE_INSPECT_S[side]}(#{left.inspect}, #{right.inspect})"
125 end
126end
127
128def explode(pair)
129 return false unless pair.is_a?(Pair)
130
131 if pair.depth >= 4 && pair.plain?
132 pair.add_ancestor_left pair.left
133 pair.add_ancestor_right pair.right
134 pair.parent.send("#{pair.side}=", 0)
135 return true
136 end
137
138 return true if explode(pair.left)
139 return true if explode(pair.right)
140
141 false
142end
143
144def split(pair)
145 return false unless pair.is_a?(Pair)
146
147 if pair.left.is_a?(Numeric) && pair.left >= 10
148 pair.left = Pair.new(pair, pair.left / 2, (pair.left / 2.0).ceil, :left)
149 return true
150 end
151
152 return true if split(pair.left)
153
154 if pair.right.is_a?(Numeric) && pair.right >= 10
155 pair.right = Pair.new(pair, pair.right / 2, (pair.right / 2.0).ceil, :right)
156 return true
157 end
158
159 return true if split(pair.right)
160
161 false
162end
163
164def sum(a, b)
165 result = Pair.combine a, b
166 loop do
167 exploded = explode result
168 next if exploded
169 splitted = split result
170 break unless splitted
171 end
172 result
173end
174
175pairs = ARGF.readlines.map { Pair.from_array(eval(_1)) }
176part2 = pairs.map(&:dup)
177
178puts pairs.reduce { sum _1, _2 }.magnitude
179puts part2.permutation(2).map { sum(_1.dup, _2.dup).magnitude }.max