Skip to content

Commit 6704bc8

Browse files
authored
Merge pull request #408 from Manasi2001/issue-407
Cryptarithmetic Problem Solving (Python)
2 parents cac8a02 + fd3dd99 commit 6704bc8

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
'''
2+
Cryptarithmetic problems are mathematical puzzles where digits are replaced by symbols.
3+
And the aim is to find unique digits(0-9) that the letters should represent, such that
4+
they satisfy the given constraints.
5+
6+
The cryptarithmetic problem that is needed to be solved here is:
7+
8+
SEND
9+
+ MORE
10+
---------
11+
= MONEY
12+
---------
13+
14+
Distinct variables are: S, E, N, D, M, O, R, Y
15+
Domain: {0,...,9}
16+
17+
'''
18+
19+
# importing the necessary libraries
20+
from typing import Generic, TypeVar, Dict, List, Optional
21+
from abc import ABC, abstractmethod
22+
23+
# declaring a type variable V as variable type and D as domain type
24+
V = TypeVar('V') # variable type
25+
D = TypeVar('D') # domain type
26+
27+
# this is a Base class for all constraints
28+
class Constraint(Generic[V, D], ABC):
29+
# the variables that the constraint is between
30+
def __init__(self, variables: List[V]) -> None:
31+
self.variables = variables
32+
33+
# this is an abstract method which must be overridden by subclasses
34+
@abstractmethod
35+
def satisfied(self, assignment: Dict[V, D]) -> bool:
36+
...
37+
38+
# A constraint satisfaction problem consists of variables of type V
39+
# that have ranges of values known as domains of type D and constraints
40+
# that determine whether a particular variable's domain selection is valid
41+
class CSP(Generic[V, D]):
42+
def __init__(self, variables: List[V], domains: Dict[V, List[D]]) -> None:
43+
# variables to be constrained
44+
# assigning variables parameter to self.variables
45+
self.variables: List[V] = variables
46+
# domain of each variable
47+
# assigning domains parameter to self.domains
48+
self.domains: Dict[V, List[D]] = domains
49+
# assigning an empty dictionary to self.constraints
50+
self.constraints: Dict[V, List[Constraint[V, D]]] = {}
51+
# iterating over self.variables
52+
for variable in self.variables:
53+
self.constraints[variable] = []
54+
# if the variable is not in domains, then raise a LookupError("Every variable should have a domain assigned to it.")
55+
if variable not in self.domains:
56+
raise LookupError("Every variable should have a domain assigned to it.")
57+
# this method adds constraint to variables as per their domains
58+
def add_constraint(self, constraint: Constraint[V, D]) -> None:
59+
for variable in constraint.variables:
60+
if variable not in self.variables:
61+
raise LookupError("Variable in constraint not in CSP")
62+
else:
63+
self.constraints[variable].append(constraint)
64+
65+
# checking if the value assignment is consistent by checking all constraints
66+
# for the given variable against it
67+
def consistent(self, variable: V, assignment: Dict[V, D]) -> bool:
68+
# iterating over self.constraints[variable]
69+
for constraint in self.constraints[variable]:
70+
# if constraint not satisfied then returning False
71+
if not constraint.satisfied(assignment):
72+
return False
73+
# otherwise returning True
74+
return True
75+
76+
# this method is performing the backtracking search to find the result
77+
def backtracking_search(self, assignment: Dict[V, D] = {}) -> Optional[Dict[V, D]]:
78+
# assignment is complete if every variable is assigned (our base case)
79+
if len(assignment) == len(self.variables):
80+
return assignment
81+
82+
# get all variables in the CSP but not in the assignment
83+
unassigned: List[V] = [v for v in self.variables if v not in assignment]
84+
85+
# get the every possible domain value of the first unassigned variable
86+
first: V = unassigned[0]
87+
# iterating over self.domains[first]
88+
for value in self.domains[first]:
89+
local_assignment = assignment.copy()
90+
# assign the value
91+
local_assignment[first] = value
92+
# if we're still consistent, we recurse (continue)
93+
if self.consistent(first, local_assignment):
94+
# recursively calling the self.backtracking_search method based on the local_assignment
95+
result: Optional[Dict[V, D]] = self.backtracking_search(local_assignment)
96+
# if we didn't find the result, we will end up backtracking
97+
if result is not None:
98+
return result
99+
return None
100+
101+
# SendMoreMoneyConstraint is a subclass of Constraint class
102+
class SendMoreMoneyConstraint(Constraint[str, int]):
103+
104+
def __init__(self, letters: List[str]) -> None:
105+
super().__init__(letters)
106+
self.letters: List[str] = letters
107+
108+
def satisfied(self, assignment: Dict[str, int]) -> bool:
109+
# if there are duplicate values then it's not a solution
110+
if len(set(assignment.values())) < len(assignment):
111+
return False
112+
113+
# if all variables have been assigned, check if it adds correctly
114+
if len(assignment) == len(self.letters):
115+
s: int = assignment["S"]
116+
e: int = assignment["E"]
117+
n: int = assignment["N"]
118+
d: int = assignment["D"]
119+
m: int = assignment["M"]
120+
o: int = assignment["O"]
121+
r: int = assignment["R"]
122+
y: int = assignment["Y"]
123+
send: int = s * 1000 + e * 100 + n * 10 + d
124+
more: int = m * 1000 + o * 100 + r * 10 + e
125+
money: int = m * 10000 + o * 1000 + n * 100 + e * 10 + y
126+
return send + more == money
127+
return True # no conflict
128+
129+
if __name__ == "__main__":
130+
letters: List[str] = ["S", "E", "N", "D", "M", "O", "R", "Y"]
131+
possible_digits: Dict[str, List[int]] = {}
132+
print("******************************************************************")
133+
print("\nHere are the results:\n")
134+
for letter in letters:
135+
possible_digits[letter] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
136+
possible_digits["M"] = [1] # so we don't get answers starting with a 0
137+
csp: CSP[str, int] = CSP(letters, possible_digits)
138+
csp.add_constraint(SendMoreMoneyConstraint(letters))
139+
solution: Optional[Dict[str, int]] = csp.backtracking_search()
140+
if solution is None:
141+
print("No solution found!")
142+
else:
143+
print(solution)
144+
print("\n******************************************************************")
145+
146+
'''
147+
Sample working:
148+
149+
******************************************************************
150+
151+
Here are the results:
152+
153+
{'S': 9, 'E': 5, 'N': 6, 'D': 7, 'M': 1, 'O': 0, 'R': 8, 'Y': 2}
154+
155+
******************************************************************
156+
157+
'''

0 commit comments

Comments
 (0)