Quantcast
Channel: geekoverdose
Viewing all articles
Browse latest Browse all 70

Yet another small number-equation-puzzle with a solver in Prolog

$
0
0

This post is about yet another small number puzzle, and the corresponding solver for it in Prolog. The post and code is written to be easily understandable and help learn the basics of “think and code” in Prolog.

number_puzzle_example

The number puzzle was sent to me by a friend a while ago. It’s a typical small number and equation puzzle: each row and column presents an equation (6 equations in total) of basic math with addition, subtraction, multiplication, and division. There are 9 numbers involved in those equations in total (3 in each row/column), and each number consists of 1 to 4 digits. Digits are numeric and are in between 0 and 9. The interesting detail in this puzzle is that certain digits are marked to be the same across different numbers in the puzzle. This is the core aspect that significantly reduces both the solution space and also the corresponding search space (across all possible numbers), and is why the puzzle becomes easy to solve by humans, and is also the reason why it only has one solution.

Swi-Prolog logo: http://www.swi-prolog.org/icons/swipl.pngHow to implement a solver for such a puzzle in Prolog? The solution should consist of the 9 numbers, so those are the variable we are going to ask for. When we ask for valid numbers, those are going to be composed of individual digits. So we need to define those digits (that they are numbers from 0-9), and also, that those digits add up to one number by factors of 1, 10, 100, and 1000. Two important details here are: digits that are the same across different numbers can be defined as being “the other digit”. This means: we do not need to search for two digits separately and then check if they are the same. We can instead just search for one and define the second one to be the same. This small details significantly reduces the search space, as it reduces the amount of variables we need to consider. The second detail is that one digit, and in our case this means also the corresponding number, cannot be zero, as it is used as a divisor in an equation (row 2, left hand side part 2).

Once the numbers and corresponding digits are defined, the 6 equations can be defined. They essentially just check if the previously found numbers fulfill the equations. And because of speedup we are not going to define all numbers and digits beforehand, but we are going to define a portion and validate it immediately, like: define digits of row 1, define numbers of row 1, check if equation of row 1 is fulfilled, an so on.

For readability, in the solver numbers are called by their row and position, e.g. row 1, left hand side 1 (R1Lhs1), down to row 3, right hand side (R3Rhs). The digits inside those numbers are called in a similar way: row 1, left hand side 1, digit 1 (R1Lhs1D1), down to row 3, right hand side, digit 3 (R3RhsD3). Note that digit one (D1) always refers to the unit position/character in the number, digit two (D2) to the ten’s position/character, digit three (D3) to the hundred’s position/character, etc.

Here’s the solver (the code is written to be easily understandable, some aspects can be optimized, like the amount of variables, the order of statement, etc):

% Prolog solver for yet another small number and equation puzzle.
% Rainhard Findling
% 2019/04
%
% Run the solver: step 1: load the knowledge database:
%     ?- ['number_puzzle.pl'].
%     true.
% Step 2: ask for the solution:
%     ?- puzzle(R1Lhs1, R1Lhs2, R1Rhs, R2Lhs1, R2Lhs2, R2Rhs, R3Lhs1, R3Lhs2, R3Rhs).
%
%     R1Lhs1 = 7,
%     R1Lhs2 = 189,
%     R1Rhs = 1323,
%     R2Lhs1 = 643,
%     R2Lhs2 = 9,
%     R2Rhs = 652,
%     R3Lhs1 = 650,
%     R3Lhs2 = 21,
%     R3Rhs = 671 ;
%
nr(X) :- between(0,9,X).
puzzle(R1Lhs1, R1Lhs2, R1Rhs, R2Lhs1, R2Lhs2, R2Rhs, R3Lhs1, R3Lhs2, R3Rhs) :-
    % all individual digits are numbers 0-9, an certain digits are the same
    % row 1 digits
    nr(R1Lhs1D1),
    nr(R1Lhs2D1),
    nr(R1Lhs2D2),
    nr(R1Lhs2D3),
    nr(R1RhsD1),
    nr(R1RhsD2),
    R1RhsD3 is R1RhsD1,
    R1RhsD4 is R1Lhs2D3,
    % blocks are composed from those numbers
    % row 1 blocks
    R1Lhs1 is R1Lhs1D1,
    R1Lhs2 is R1Lhs2D1 * 1 + R1Lhs2D2 * 10 + R1Lhs2D3 * 100,
    R1Rhs  is R1RhsD1  * 1 + R1RhsD2  * 10 + R1RhsD3  * 100 + R1RhsD4 * 1000,
    % row 1 equation
    R1Rhs is R1Lhs1 * R1Lhs2,
    % Row 2 digits
    R2Lhs1D1 is R1RhsD1,
    nr(R2Lhs1D2),
    nr(R2Lhs1D3),
    R2Lhs2D1 is R1Lhs2D1,
    % special clause: as R2Lhs2D1 is a divisor in the equations it cannot be 0
    R2Lhs2D1 \= 0,
    R2RhsD1 is R1RhsD2,
    nr(R2RhsD2),
    R2RhsD3 is R2Lhs1D3,
    % row 2 blocks
    R2Lhs1 is R2Lhs1D1 * 1 + R2Lhs1D2 * 10 + R2Lhs1D3 * 100,
    R2Lhs2 is R2Lhs2D1,
    R2Rhs is R2RhsD1 * 1 + R2RhsD2 * 10 + R2RhsD3 * 100,
    % row 2 equation
    R2Rhs is R2Lhs1 + R2Lhs2,
    % row 3 digits
    nr(R3Lhs1D1),
    R3Lhs1D2 is R2RhsD2,
    R3Lhs1D3 is R2Lhs1D3,
    R3Lhs2D1 is R1RhsD4,
    R3Lhs2D2 is R2RhsD1,
    R3RhsD1 is R3Lhs2D1,
    R3RhsD2 is R1Lhs1D1,
    R3RhsD3 is R3Lhs1D3,
    % row 3 blocks
    R3Lhs1 is R3Lhs1D1 * 1 + R3Lhs1D2 * 10 + R3Lhs1D3 * 100,
    R3Lhs2 is R3Lhs2D1 * 1 + R3Lhs2D2 * 10,
    R3Rhs  is R3RhsD1  * 1 + R3RhsD2  * 10 + R3RhsD3  * 100,
    % row 3 equations
    R3Rhs is R3Lhs1 + R3Lhs2,
    % column equations
    R3Lhs1 is R1Lhs1 + R2Lhs1,
    R3Lhs2 is R1Lhs2 / R2Lhs2,
    R3Rhs is R1Rhs - R2Rhs.

The code can be run in Prolog (swipl) with first loading the database, then asking for the solution, which yields exactly one correct solution:

?- ['number_puzzle.pl'].
true.

?- puzzle(R1Lhs1, R1Lhs2, R1Rhs, R2Lhs1, R2Lhs2, R2Rhs, R3Lhs1, R3Lhs2, R3Rhs).
R1Lhs1 = 7,
R1Lhs2 = 189,
R1Rhs = 1323,
R2Lhs1 = 643,
R2Lhs2 = 9,
R2Rhs = 652,
R3Lhs1 = 650,
R3Lhs2 = 21,
R3Rhs = 671 ;

Hence the solution for the puzzle is, row-wise: 7*189=1323,642+9=652, and 650+21=671, or column-wise: 7+643=650, 189/9=21, and 1323-643=671. Done! 🙂


Viewing all articles
Browse latest Browse all 70

Trending Articles