1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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)