monkeys = ARGF.read.lines(chomp: true).to_h {|line| line.split(": ") } # part one # monkeys.each do |name, body| # define_method(name) do # eval(body) # end # end # p root # part two sub_monkeys = monkeys.delete("root").split(" + ") monkeys.delete("humn") require "z3" solver = Z3::Solver.new solver.assert Z3.Int(sub_monkeys[0]) == Z3.Int(sub_monkeys[1]) monkeys.each do |name, job| a, op, b = job.split(" ") if op.nil? solver.assert Z3.Int(name) == a.to_i else a = a =~ /^\d+$/ ? a.to_i : Z3.Int(a) b = b =~ /^\d+$/ ? b.to_i : Z3.Int(b) solver.assert Z3.Int(name) == a.send(op, b) end end fail unless solver.satisfiable? p solver.model[Z3.Int("humn")] exit # original part two monkeys.each do |name, body| define_method(name) do eval(body) end end target, unknown = sub_monkeys.partition { eval(_1) rescue nil}.map(&:first) target = eval(target) monkeys = monkeys.to_h {|monkey, job| a, op, b = job.split(" ") [monkey, { op:, a:, b: }] } monkeys["humn"] = { op: :humn } def apply(monkeys, monkey) job = monkeys.fetch(monkey) case op = job.fetch(:op) when nil job.fetch(:a).to_i when :humn :humn else a = apply(monkeys, job.fetch(:a)) b = apply(monkeys, job.fetch(:b)) if a.is_a?(Integer) && b.is_a?(Integer) eval([a, op, b].join(" ")) else [job.fetch(:op), a, b] end end end tree = apply(monkeys, unknown) def reverse(target, tree) loop do case tree in :humn return target in [op, Integer => a, b] case op when ?* then target /= a when ?+ then target -= a when ?- then target = a - target else fail tree.inspect end tree = b in [op, a, Integer => b] case op when ?- then target += b when ?/ then target *= b when ?* then target /= b when ?+ then target -= b else fail tree.inspect end tree = a else fail tree.inspect end end end p reverse(target, tree)