2

I'm studying about algorithms and recently found the interesting challenge.

It will give us some row/column, and our mission is to fill table with integer 1~N which displays only once and their row and column sums are equal to given row/column.

The challenge simple example:

    [ ]  [ ]  [ ]   13
    [ ]  [ ]  [ ]    8
    [ ]  [ ]  [ ]   24
     14   14   17

answer:

    [2]  [6]  [5]   13
    [3]  [1]  [4]    8
    [9]  [7]  [8]   24
     14   14   17

Thanks

Jonnus
  • 2,988
  • 2
  • 24
  • 33
ansogpo1
  • 39
  • 4

4 Answers4

4

As far as I know there is no straightforward algorithm to solve this specific problem more efficiently than using a backtracking approach.

You can however do this more intelligently than simply enumerating over all possible solutions. An efficient way to do this is Constraint Programming (CP) (or derived paradigms like Constraint Logic Programming (CLP)). Basically it comes down on reasoning about the constraints you have put on your problem trying to reduce the domain of the variables.

After reducing the domains, you make a choice on which you can later backtrack. After making such choice you again reduce domains and possibly have to make additional choices.

You can for instance use ECLiPSe (not the IDE, but a constraint logic programming tool) for this:

:- lib(ic).
:- import alldifferent/1 from ic_global.
:- import sumlist/2 from ic_global.

solve(Problem) :-
    problem(Problem,N,LA,LB),
    puzzle(N,LA,LB,Grid),
    print_Grid(Grid).

puzzle(N,LA,LB,Grid) :-
    N2 is N*N,
    dim(Grid,[N,N]),
    Grid[1..N,1..N] :: 1..N2,
    (for(I,1,N), param(N,Grid,LA,LB) do
        Sc is nth1(I,LA),
        Lc is Grid[1..N,I],
        sumlist(Lc,Sc),
        Sr is nth1(I,LB),
        Lr is Grid[I,1..N],
        sumlist(Lr,Sr)
    ),
    term_variables(Grid,Vars),
    alldifferent(Vars),
    labeling(Vars).

print_Grid(Grid) :-
    dim(Grid,[N,N]),
    ( for(I,1,N), param(Grid,N) do
        ( for(J,1,N), param(Grid,I) do
            X is Grid[I,J],
        ( var(X) -> write("  _") ; printf(" %2d", [X]) )
        ), nl
    ), nl.

nth1(1,[H|_],H) :- !.
nth1(I,[_|T],H) :-
    I1 is I-1,
    nth1(I1,T,H).

problem(1,3,[14,14,17],[13,8,24]).

The program is vaguely based on my implementation for multi-sudoku. Now you can solve the problem using ECLiPSe:

ECLiPSe Constraint Logic Programming System [kernel]
Kernel and basic libraries copyright Cisco Systems, Inc.
and subject to the Cisco-style Mozilla Public Licence 1.1
(see legal/cmpl.txt or http://eclipseclp.org/licence)
Source available at www.sourceforge.org/projects/eclipse-clp
GMP library copyright Free Software Foundation, see legal/lgpl.txt
For other libraries see their individual copyright notices
Version 6.1 #199 (x86_64_linux), Sun Mar 22 09:34 2015
[eclipse 1]: solve(1).
lists.eco  loaded in 0.00 seconds
WARNING: module 'ic_global' does not exist, loading library...
queues.eco loaded in 0.00 seconds
ordset.eco loaded in 0.00 seconds
heap_array.eco loaded in 0.00 seconds
graph_algorithms.eco loaded in 0.03 seconds
max_flow.eco loaded in 0.00 seconds
flow_constraints_support.eco loaded in 0.00 seconds
ic_sequence.eco loaded in 0.01 seconds
ic_global.eco loaded in 0.05 seconds
  2  5  6
  3  1  4
  9  8  7


Yes (0.05s cpu, solution 1, maybe more) ? ;
  5  2  6
  1  3  4
  8  9  7


Yes (0.05s cpu, solution 2, maybe more) ? ;
  2  6  5
  3  1  4
  9  7  8


Yes (0.05s cpu, solution 3, maybe more) ? ;
  3  6  4
  2  1  5
  9  7  8


Yes (0.05s cpu, solution 4, maybe more) ? ;
  6  2  5
  1  3  4
  7  9  8


Yes (0.05s cpu, solution 5, maybe more) ? ;
  6  3  4
  1  2  5
  7  9  8


Yes (0.05s cpu, solution 6, maybe more) ? ;
  2  6  5
  4  1  3
  8  7  9


Yes (0.05s cpu, solution 7, maybe more) ? ;
  4  6  3
  2  1  5
  8  7  9


Yes (0.05s cpu, solution 8, maybe more) ? 
  6  2  5
  1  4  3
  7  8  9


Yes (0.05s cpu, solution 9, maybe more) ? ;
  6  4  3
  1  2  5
  7  8  9


Yes (0.05s cpu, solution 10, maybe more) ? ;

No (0.06s cpu)

One simply queries solve(1) and the constraint logic programming tool does the rest. There are thus a total of 10 solutions.

Note that the program works for an arbitrary N, although - since worst case this program performs backtracking - evidently the program can only solve the problems for a reasonable N.

Community
  • 1
  • 1
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    Seriously, nice answer! I tried to add a bit more background information in mine, but this is a great answer in the sense that you've also provided OP with working code and a basis to start off from. _Mooi werk :)_. – Nelewout Jan 12 '16 at 11:19
3

Oh, I just love it when these little optimisation problems pop up. They always remind me of that one time in my very first year when I built a thing that would solve Sudoku's and had a ton of fun with it! You may guess how many sudoku's I've solved ever since :).


Now, your problem is an ILP (Integer Linear Program). Even before you read up on that article, you should take note that ILP's are hard. Restricting the solution space to N or Z is severely limiting and oftentimes, such a solution does not exist!

For your problem, the task at hand essentially boils down to solving this,

Minimise 0 (arbitrary objective function)

Subject to,

x1 + x2 + x3 = 13
x4 + x5 + x6 = 8
x7 + x8 + x9 = 24

x1 + x4 + x7 = 14
x2 + x5 + x8 = 14
x3 + x6 + x9 = 17

And,

x_i in N, x_i distinct.

In matrix form, these equations become,

    |1   1   1   0   0   0   0   0   0|
    |0   0   0   1   1   1   0   0   0|
A = |0   0   0   0   0   0   1   1   1|
    |1   0   0   1   0   0   1   0   0|
    |0   1   0   0   1   0   0   1   0|
    |0   0   1   0   0   1   0   0   1|

And,

    |13|
    | 8|
B = |24|
    |14|
    |14|
    |17|

Such that the constraints reduce to A*x = B. So the problem we want to solve can now equivalently be written as,

Minimise 0

Subject to,

A * x = B

And,

x in N^7, x_i distinct.

Does this look hard to you? If not, think about this: the real line is huge, and on that line, every once in a while, is a tiny dot. That's an integer. We need some of those. We do not know which ones. So essentially, a perfect analogy would be looking for a needle in a haystack.

Now, do not despair, we are surprisingly good at finding these ILP needles! I just want you to appreciate the nontrivial difficulty of the field this problem stems from.

I want to give you working code, but I do not know your preferred choice of language/toolkit. If this is just a hobbyist approach, even Excel's solver would work beautifully. If it is not, I do not think I could've phrased it any better than Willem Van Onsem already has, and I would like to direct you to his answer for an implementation.

Nelewout
  • 6,281
  • 3
  • 29
  • 39
  • 1
    I hadn't considered ILP, but indeed this will definitely work. *Goed gedaan*! ;) +1. – Willem Van Onsem Jan 12 '16 at 11:21
  • I'm concerned about your "arbitrary" objective function. This won't work since the objective is not a function of constraint variables. If you do add constraints `x_i >= 0`, the return value will be 0 without working on any constraints! Check: http://nl.mathworks.com/help/optim/ug/linprog.html or any text book or wiki as well. – Erobrere Jan 12 '16 at 18:47
  • @Erobrere that very much depends on your solver. This is a correct problem specification in any ILP setting, as the constraints still need to hold, regardless of the objective function; it just needs to be translated into solver-ready code. Which I did not dare to do, since the OP did not give _any_ indication on the software he desires to use. – Nelewout Jan 12 '16 at 19:07
2

Below is another Constraint Programming model, using a similar approach as Willem Van Onsem's solution, i.e. using the global constraint "all_different", which is an efficient method to ensure that the numbers in the matrix are assigned only once. (The concept of "global constraints" is very important in CP and there is much research finding fast implementations for different kind of common constraints.)

Here's a MiniZinc model: http://hakank.org/minizinc/matrix_puzzle.mzn

include "globals.mzn"; 
% parameters
int: rows;
int: cols;
array[1..rows] of int: row_sums;
array[1..cols] of int: col_sums;

% decision variables
array[1..rows,1..cols] of var 1..rows*cols: x;

solve satisfy;

constraint
  all_different(array1d(x)) /\
  forall(i in 1..rows) (
    all_different([x[i,j] | j in 1..cols]) /\
    sum([x[i,j] | j in 1..cols]) = row_sums[i]
  )
  /\
  forall(j in 1..cols) (
    all_different([x[i,j] | i in 1..rows]) /\
    sum([x[i,j] | i in 1..rows]) = col_sums[j]
  );

  output [
    if j = 1 then "\n" else " " endif ++
      show_int(2,x[i,j])
    | i in 1..rows, j in 1..cols
  ];

  % Problem instance
  rows = 3;
  cols = 3;
  row_sums = [13,8,24];
  col_sums = [14,14,17];

Here are the first two (of 10) solutions:

 2  5  6
 3  1  4
 9  8  7
 ----------

 5  2  6
 1  3  4
 8  9  7
 ----------
 ...

An additional comment: A fun thing with CP - as well as an important concept - is that it is possible to generate new problem instances using almost the identical model: http://hakank.org/minizinc/matrix_puzzle2.mzn

The only difference is the following lines, i.e. change "row_sums" and "col_sums" to decision variables and comment the hints.

  array[1..rows] of var int: row_sums; % add "var"
  array[1..cols] of var int: col_sums; % add "var"

  % ...

  % row_sums = [13,8,24];
  % col_sums = [14,14,17];

Here are three generated problem instances (of 9!=362880 possible):

  row_sums: [21, 15, 9]
  col_sums: [19, 15, 11]

  5  9  7
  8  4  3
  6  2  1
  ----------
  row_sums: [20, 16, 9]
  col_sums: [20, 14, 11]

  5  8  7
  9  4  3
  6  2  1
  ----------
  row_sums: [22, 14, 9]
  col_sums: [18, 15, 12]

  5  9  8
  7  4  3
  6  2  1
  ----------
hakank
  • 6,629
  • 1
  • 17
  • 27
  • And here's a CP model in Picat: http://hakank.org/picat/matrix_puzzle.pi It has three "modes": a) solve and show all solutions for the specific problem instance, b) generate a random 30x30 problem instance, and c) count the number of solutions on 3x3 matrices. – hakank Jan 12 '16 at 17:45
-1

I think the backtracking alghorithm would work very well here.

Altough the backtracking is still "brute-force", it can be really fast in average case. For example solving SUDOKU with backtracking usually takes only 1000-10000 iterations (which is really fast considering that the O-complexity is O(9^n), where n are empty spaces, therefore average sudoku have about ~ 9^60 possibilities, which would take years on average computer to finish).

This task has a lot of rules (uniqueness of numbers and sum at rows/cols) which is quite good for bactracking. More rules = more checking after each step and throwing away branches that cant bring a solution.

This can help : https://en.wikipedia.org/wiki/Sudoku_solving_algorithms

libik
  • 22,239
  • 9
  • 44
  • 87