Open In App

Solving Cryptarithmetic Puzzles

Newspapers and magazines often have crypt-arithmetic puzzles of the form:

  SEND
+ MORE
--------
MONEY
--------

 The goal here is to assign each letter a digit from 0 to 9 so that the arithmetic works out correctly. The rules are that all occurrences of a letter must be assigned the same digit, and no digit can be assigned to more than one letter.

/* 
ExhaustiveSolve
---------------
This is the "not-very-smart" version of a cryptarithmetic solver.
It takes the puzzle itself (with the 3 strings for the two addends and sum)
and a string of letters as yet unassigned. If no more letters to assign,
then we've hit a base-case. If the current letter-to-digit mapping solves
the puzzle, we're done. Otherwise, we return False to trigger backtracking.
If we have letters to assign, we take the first letter from that list and
try assigning it the digits from 0 to 9 and then recursively work
through solving the puzzle from here. If we manage to make a good assignment
that works, we've succeeded. Otherwise, we need to unassign that choice and try
another digit. This version is easy to write since it uses a simple
approach (quite similar to permutations if you think about it), but it is
not so smart because it doesn't take into account the structure of the
puzzle constraints (for example, once the two digits for the addends have
been assigned, there is no reason to try anything other than the correct
digit for the sum), yet it tries a lot of useless combos regardless.
*/

bool exhaustive_solve(Puzzle& puzzle, std::string letters_to_assign) {
    if (letters_to_assign.empty()) {  // no more choices to make
        return puzzle_solved(puzzle);  // checks arithmetic to see if it works
    }
    for (int digit = 0; digit < 10; ++digit) {  // try all digits
        if (assign_letter_to_digit(letters_to_assign[0], digit)) {
            if (exhaustive_solve(puzzle, letters_to_assign.substr(1))) {
                return true;
            }
            unassign_letter_from_digit(letters_to_assign[0], digit);
        }
    }
    return false;  // nothing worked, need to backtrack
}
/* ExhaustiveSolve
* ---------------
* This is the &quot;not-very-smart&quot; version of cryptarithmetic solver. It takes
* the puzzle itself (with the 3 strings for the two addends and sum) and a
* string of letters as yet unassigned. If no more letters to assign
* then we've hit a base-case, if the current letter-to-digit mapping solves
* the puzzle, we're done, otherwise we return false to trigger backtracking
* If we have letters to assign, we take the first letter from that list, and
* try assigning it the digits from 0 to 9 and then recursively working
* through solving puzzle from here. If we manage to make a good assignment
* that works, we've succeeded, else we need to unassign that choice and try
* another digit. This version is easy to write, since it uses a simple
* approach (quite similar to permutations if you think about it) but it is
* not so smart because it doesn't take into account the structure of the
* puzzle constraints (for example, once the two digits for the addends have
* been assigned, there is no reason to try anything other than the correct
* digit for the sum) yet it tries a lot of useless combos regardless
*/
bool ExhaustiveSolve(puzzleT puzzle, string lettersToAssign)
{
    if (lettersToAssign.empty()) // no more choices to make
        return PuzzleSolved(puzzle); // checks arithmetic to see if works
    for (int digit = 0; digit &lt;= 9; digit++)   // try all digits
    {
        if (AssignLetterToDigit(lettersToAssign[0], digit))
        {
            if (ExhaustiveSolve(puzzle, lettersToAssign.substr(1)))
                return true;
            UnassignLetterFromDigit(lettersToAssign[0], digit);
        }
    }
    return false;  // nothing worked, need to backtrack
}
 /* ExhaustiveSolve
* ---------------
* This is the "not-very-smart" version of cryptarithmetic solver. It takes
* the puzzle itself (with the 3 strings for the two addends and sum) and a
* string of letters as yet unassigned. If no more letters to assign
* then we've hit a base-case, if the current letter-to-digit mapping solves
* the puzzle, we're done, otherwise we return false to trigger backtracking
* If we have letters to assign, we take the first letter from that list, and
* try assigning it the digits from 0 to 9 and then recursively working
* through solving puzzle from here. If we manage to make a good assignment
* that works, we've succeeded, else we need to unassign that choice and try
* another digit. This version is easy to write, since it uses a simple
* approach (quite similar to permutations if you think about it) but it is
* not so smart because it doesn't take into account the structure of the
* puzzle constraints (for example, once the two digits for the addends have
* been assigned, there is no reason to try anything other than the correct
* digit for the sum) yet it tries a lot of useless combos regardless
*/
static boolean exhaustiveSolve(Puzzle puzzle, String lettersToAssign) {
    if (lettersToAssign.isEmpty()) // no more choices to make
        return puzzleSolved(puzzle); // checks arithmetic to see if works
    for (int digit = 0; digit <= 9; digit++) // try all digits
    {
        if (assignLetterToDigit(lettersToAssign.charAt(0), digit)) {
            if (exhaustiveSolve(puzzle, lettersToAssign.substring(1)))
                return true;
            unassignLetterFromDigit(lettersToAssign.charAt(0), digit);
        }
}
    return false; // nothing worked, need to backtrack
}
//this code is contributed by abdulaslam
"""
ExhaustiveSolve
---------------
This is the "not-very-smart" version of a cryptarithmetic solver.
It takes the puzzle itself (with the 3 strings for the two addends and sum)
and a string of letters as yet unassigned. If no more letters to assign,
then we've hit a base-case. If the current letter-to-digit mapping solves
the puzzle, we're done. Otherwise, we return False to trigger backtracking.
If we have letters to assign, we take the first letter from that list and
try assigning it the digits from 0 to 9 and then recursively work
through solving the puzzle from here. If we manage to make a good assignment
that works, we've succeeded. Otherwise, we need to unassign that choice and try
another digit. This version is easy to write since it uses a simple
approach (quite similar to permutations if you think about it), but it is
not so smart because it doesn't take into account the structure of the
puzzle constraints (for example, once the two digits for the addends have
been assigned, there is no reason to try anything other than the correct
digit for the sum), yet it tries a lot of useless combos regardless.
"""
def exhaustive_solve(puzzle, letters_to_assign):
    if not letters_to_assign:  # no more choices to make
        return puzzle_solved(puzzle)  # checks arithmetic to see if it works
    for digit in range(10):  # try all digits
        if assign_letter_to_digit(letters_to_assign[0], digit):
            if exhaustive_solve(puzzle, letters_to_assign[1:]):
                return True
            unassign_letter_from_digit(letters_to_assign[0], digit)
    return False  # nothing worked, need to backtrack

#This code is contributed by abdulaslam
/*
ExhaustiveSolve
---------------
This is the "not-so-smart" version of a cryptarithmetic solver.
It takes the puzzle itself (with the 3 strings for the two addends and sum)
and a string of letters as yet unassigned. If no more letters to assign,
then we've hit a base-case. If the current letter-to-digit mapping solves
the puzzle, we're done. Otherwise, we return false to trigger backtracking.
If we have letters to assign, we take the first letter from that list and
try assigning it the digits from 0 to 9 and then recursively work
through solving the puzzle from here. If we manage to make a good assignment
that works, we've succeeded. Otherwise, we need to unassign that choice and try
another digit. This version is easy to write since it uses a simple
approach (quite similar to permutations if you think about it), but it is
not so smart because it doesn't take into account the structure of the
puzzle constraints (for example, once the two digits for the addends have
been assigned, there is no reason to try anything other than the correct
digit for the sum), yet it tries a lot of useless combos regardless.
*/

public bool ExhaustiveSolve(Puzzle puzzle, string lettersToAssign)
{
    if (string.IsNullOrEmpty(lettersToAssign))  // no more choices to make
    {
        return PuzzleSolved(puzzle);  // checks arithmetic to see if it works
    }
    for (int digit = 0; digit < 10; ++digit)  // try all digits
    {
        if (AssignLetterToDigit(lettersToAssign[0], digit))
        {
            if (ExhaustiveSolve(puzzle, lettersToAssign.Substring(1)))
            {
                return true;
            }
            UnassignLetterFromDigit(lettersToAssign[0], digit);
        }
    }
    return false;  // nothing worked, need to backtrack
}
/* ExhaustiveSolve
* ---------------
* This is the "not-very-smart" version of cryptarithmetic solver. It takes
* the puzzle itself (with the 3 strings for the two addends and sum) and a
* string of letters as yet unassigned. If no more letters to assign
* then we've hit a base-case, if the current letter-to-digit mapping solves
* the puzzle, we're done, otherwise we return false to trigger backtracking
* If we have letters to assign, we take the first letter from that list, and
* try assigning it the digits from 0 to 9 and then recursively working
* through solving puzzle from here. If we manage to make a good assignment
* that works, we've succeeded, else we need to unassign that choice and try
* another digit. This version is easy to write, since it uses a simple
* approach (quite similar to permutations if you think about it) but it is
* not so smart because it doesn't take into account the structure of the
* puzzle constraints (for example, once the two digits for the addends have
* been assigned, there is no reason to try anything other than the correct
* digit for the sum) yet it tries a lot of useless combos regardless
*/
function exhaustiveSolve(puzzle, lettersToAssign) {
    if (lettersToAssign.length === 0) // no more choices to make
        return puzzleSolved(puzzle); // checks arithmetic to see if works
    for (let digit = 0; digit <= 9; digit++) // try all digits
    {
        if (assignLetterToDigit(lettersToAssign[0], digit)) {
            if (exhaustiveSolve(puzzle, lettersToAssign.substring(1)))
                return true;
            unassignLetterFromDigit(lettersToAssign[0], digit);
        }
    }
    return false; // nothing worked, need to backtrack
}
//This code is contributed by abdulaslam

The algorithm above actually has a lot in common with the permutations algorithm, it pretty much just creates all arrangements of the mapping from characters to digits and tries each until one works or all have been successfully tried. For a large puzzle, this could take a while. A smarter algorithm could take into account the structure of the puzzle and avoid going down dead-end paths. For example, if we assign the characters starting from the one's place and moving to the left, at each stage, we can verify the correctness of what we have so far before we continue onwards. This definitely complicates the code but leads to a tremendous improvement in efficiency, making it much more feasible to solve large puzzles. Below pseudocode, in this case, has more special cases, but the same general design

Source: http://see.stanford.edu/materials/icspacs106b/H19-RecBacktrackExamples.pdf

Article Tags :