#!/usr/bin/env python3
"""NUMBERPLAY
Given
(1) N != U != M != B != E != R != P != L != A != Y
(2) N != B != P != 0
Find all valid solutions to NUM + BER = PLAY
"""
class Solution(object):
def __init__(
self,
*,
n=None,
u=None,
m=None,
b=None,
e=None,
r=None,
p=None,
l=None,
a=None,
y=None,
carry=0
):
self._validate("n", n, allow_none=True)
self.n = n
self._validate("u", u, allow_none=True)
self.u = u
self._validate("m", m, allow_none=True)
self.m = m
self._validate("b", b, allow_none=True)
self.b = b
self._validate("e", e, allow_none=True)
self.e = e
self._validate("r", r, allow_none=True)
self.r = r
self._validate("p", p, allow_none=True)
self.p = p
self._validate("l", l, allow_none=True)
self.l = l
self._validate("a", a, allow_none=True)
self.a = a
self._validate("y", y, allow_none=True)
self.y = y
if carry not in [0,1]:
raise ValueError("carry must be 0 or 1")
self.carry = carry
def __str__(self):
_n = str(self.n) if self.n is not None else "N"
_u = str(self.u) if self.u is not None else "U"
_m = str(self.m) if self.m is not None else "M"
_b = str(self.b) if self.b is not None else "B"
_e = str(self.e) if self.e is not None else "E"
_r = str(self.r) if self.r is not None else "R"
_p = str(self.p) if self.p is not None else "P"
_l = str(self.l) if self.l is not None else "L"
_a = str(self.a) if self.a is not None else "A"
_y = str(self.y) if self.y is not None else "Y"
return f"{_n}{_u}{_m} + {_b}{_e}{_r} = {_p}{_l}{_a}{_y}"
def __getitem__(self, key):
if key == "n":
return self.n
elif key == "u":
return self.u
elif key == "m":
return self.m
elif key == "b":
return self.b
elif key == "e":
return self.e
elif key == "r":
return self.r
elif key == "p":
return self.p
elif key == "l":
return self.l
elif key == "a":
return self.a
elif key == "y":
return self.y
else:
raise AttributeError(f"no position '{key}'")
def __setitem__(self, key, value):
if self[key] is not None:
raise AttributeError(f"position '{key}' is already set")
self._validate(key, value)
if key == "n":
self.n = value
elif key == "u":
self.u = value
elif key == "m":
self.m = value
elif key == "b":
self.b = value
elif key == "e":
self.e = value
elif key == "r":
self.r = value
elif key == "p":
self.p = value
elif key == "l":
self.l = value
elif key == "a":
self.a = value
elif key == "y":
self.y = value
else:
raise AttributeError(f"no position '{key}'")
def copy(self):
"""Duplicate this solution."""
return Solution(n=self.n, u=self.u, m=self.m, b=self.b, e=self.e,
r=self.r, p=self.p, l=self.l, a=self.a, y=self.y, carry=self.carry)
def is_using(self, n):
"""Check if n is already used in the solution."""
if not isinstance(n, int) or n < 0 or 9 < n:
raise ValueError("values must be integers between 0 and 9")
used = []
for position in ["n", "u", "m", "b", "e", "r", "p", "l", "a", "y"]:
try:
used.append(self[position])
except AttributeError:
pass
return n in used
def _validate(self, position, value, allow_none=False):
"""Validate a value for an position."""
if not isinstance(value, int):
if allow_none == False:
raise ValueError("value must be an integer")
elif value is not None:
raise ValueError("value must be an integer or None")
else:
if value < 0 or 9 < value:
raise ValueError("value must be an integer between 0 and 9")
elif position in ["n", "b", "p"] and value == 0:
raise ValueError("value must be an integer between 1 and 9")
elif self.is_using(value):
raise ValueError("value must be unique")
def add_digits(a, b, carry):
sum = a + b + carry
new_carry = (10 <= sum)
c = int(str(sum)[-1])
return new_carry, c
def solve_column(partial, r1, r2, r3):
solutions = []
for a in range(0,10):
if partial.is_using(a):
continue
elif r1 == "n" and a == 0:
continue
for b in range(0,10):
if a == b or partial.is_using(b):
continue
elif r2 == "b" and b == 0:
continue
new_carry, c = add_digits(a, b, partial.carry)
if a == c or b == c or partial.is_using(c):
continue
new_solution = partial.copy()
new_solution[r1] = a
new_solution[r2] = b
new_solution[r3] = c
new_solution.carry = new_carry
solutions.append(new_solution)
return solutions
def main():
count = 0
# P = 1 (no values satisfy a + b >= 2000 where a, b <= 999)
solution = Solution(p=1)
# solve right column N U *
# + B E *
# -------
# P L A *
for s1 in solve_column(solution, 'm', 'r', 'y'):
# solve middle column N * M
# + B * R
# -------
# P L * Y
for s2 in solve_column(s1, 'u', 'e', 'a'):
# solve left column * U M
# + * E R
# -------
# P * A Y
for s3 in solve_column(s2, 'n', 'b', 'l'):
# now just filter to solutions that carry a 1 to P
if s3.carry == 1:
print(s3)
count += 1
print(f"Total: {count} solutions")
if __name__ == "__main__":
main()