On Day 21 we have input like this:
root: pppw + sjmn
dbpl: 5
cczh: sllz + lgvd
zczc: 2
ptdq: humn - dvpt
dvpt: 3
lfqf: 4
humn: 5
ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32
So we have variables that contain plain values or some operation using other variables. The task is to output the value of root.
We can write very elegant solution in Ruby using eval:
data = File.readlines("21.txt", chomp: true)
data.map do |line|
name, expression = line.match(/^(.+?): (.+?)$/).captures
eval("def #{name}; #{expression}; end")
end
puts root
We just read the input file and define new method for each variable and put the right side into it’s body.
So for the given example we will generate the following code:
def root; pppw + sjmn; end
def dbpl; 5; end
def cczh; sllz + lgvd; end
def zczc; 2; end
def ptdq; humn - dvpt; end
def dvpt; 3; end
def lfqf; 4; end
def humn; 5; end
def ljgn; 2; end
def sjmn; drzm * dbpl; end
def sllz; 4; end
def pppw; cczh / lfqf; end
def lgvd; ljgn * ptdq; end
def drzm; hmdt - zczc; end
def hmdt; 32; end
Then we just call root
method and print out the output value.
Second part is more difficult. Our root
variable should perform equality and our humn
value is something we need to calculate so this equality test is true.
We can still use the approach from Part A, but now it will be more code. Approach I used is to define humn
method to raise an special exception.
Now instead of just calling root
method, we can first call it’s left part and check if it raises this special exception. From the example above our root is defined as def root; pppw + sjmn; end
. So first we call pppw
. If exception is raised it means humn
is evaluated somewhere in pppw
tree. If not, it must be evaluate on the right side, so sjmn
in this case.
Since one side of the operation will not raise the exception, we will know it’s value. And we also know the operation, so using this we can calculate what should be the value of the other side to make it work.
Let’s use example input again. When we evaluate root
, pppw
will raise exception, but sjmn
will just return 150. Because root
operation is ==
and it must be true then pppw
must also equal 150.
We can then try to evaluate pppw
. It is defined as def pppw; cczh / lfqf; end
. Again cczh
will raise an exception, but lfqf
will just return 4. So we know that cczh / 4
must equal 150, so cczh
must equal 600. And we can continue like that until we reach humn
variable.
Here is the full code:
require "debug"
# 9584437937672
class SpecialException < StandardError; end
data = File.readlines("21.txt", chomp: true)
@operations = {}
data.map do |line|
name, expression = line.match(/^(.+?): (.+?)$/).captures
if expression =~ /\d+/
if name == "humn"
eval("def #{name}; raise SpecialException; end")
else
eval("def #{name}; #{expression}; end")
@operations[name] = expression
end
else
left, op, right = expression.match(/(.+?) (\+|\-|\/|\*) (.+)/).captures
if name == "root"
op = "=="
end
eval("def #{name}; #{left} #{op} #{right}; end")
@operations[name] = [left, op, right]
end
end
def parse(node, expected)
if node == "humn"
return expected
end
known = nil
value = nil
left, op, right = @operations[node]
begin
value = eval(left)
rescue SpecialException
known = :right
value = eval(right)
end
if known.nil?
known = :left
value = eval(left)
end
if op == "/" && known == :right
# expected = left / right
# left = expected * right
parse(left, expected * value)
elsif op == "/" && known == :left
# expected = left / right
# right = left / expected
parse(right, value / expected)
elsif op == "+" && known == :left
# expected = left + right
# right = expected - left
parse(right, expected - value)
elsif op == "+" && known == :right
# expected = left + right
# left = expected - right
parse(left, expected - value)
elsif op == "-" && known == :left
# expected = left - right
# right = left - expected
parse(right, value - expected)
elsif op == "-" && known == :right
# expected = left - right
# left = expected + right
parse(left, expected + value)
elsif op == "*" && known == :left
# expected = left * right
# right = expected / left
parse(right, expected / value)
elsif op == "*" && known == :right
# expected = left * right
# left = expected / right
parse(left, expected / value)
elsif op == "==" && known == :left
parse(right, value)
elsif op == "==" && known == :right
parse(left, value)
end
end
puts parse("root", true)