Current section

Files

Jump to
fixpoint lib examples survo_hakank.ex
Raw

lib/examples/survo_hakank.ex

#
# Survo puzzle in Elixir.
#
# http://en.wikipedia.org/wiki/Survo_Puzzle
# """
# Survo puzzle is a kind of logic puzzle presented (in April 2006) and studied
# by Seppo Mustonen. The name of the puzzle is associated to Mustonen's
# Survo system which is a general environment for statistical computing and
# related areas.
# In a Survo puzzle the task is to fill an m * n table by integers 1,2,...,m*n so
# that each of these numbers appears only once and their row and column sums are
# equal to integers given on the bottom and the right side of the table.
# Often some of the integers are given readily in the table in order to
# guarantee uniqueness of the solution and/or for making the task easier.
# """
#
# See also
# http://www.survo.fi/english/index.html
# http://www.survo.fi/puzzles/index.html
#
#
# This program was created by Hakan Kjellerstrand, hakank@gmail.com
# See also my Elixir page: http://www.hakank.org/elxir/
#
defmodule SurvoPuzzle do
# import Enum # Conflicts with CPSolver.Constraint.Factory.sum
# import CPUtils
alias CPSolver.IntVariable
alias CPSolver.Constraint.Sum
alias CPSolver.Constraint.AllDifferent
alias CPSolver.Model
def print_solution(x, rows, cols, rowsums, colsums) do
for i <- 0..(rows - 1) do
for j <- 0..(cols - 1) do
:io.format("~3w", [Enum.at(x, i * cols + j)])
end
:io.format(" = ~3w ~n", [Enum.at(rowsums, i)])
end
for j <- 0..(cols - 1) do
:io.format("~3w", [Enum.at(colsums, j)])
end
:io.format("~n~n", [])
end
#
# From http://en.wikipedia.org/wiki/Survo_Puzzle, first example
#
# Solutions should be
#
# 12 6 2 10 = 30
# 8 1 5 4 = 18
# 7 9 3 11 = 30
# 27 16 10 25
#
# I.e 12 6 2 10 8 1 5 4 7 9 3 11
#
def puzzle(1) do
rowsums = [30, 18, 30]
colsums = [27, 16, 10, 25]
# 0 is unknown -> to be decided
# 0 means unknown
problem = [[0, 6, 0, 0], [8, 0, 0, 0], [0, 0, 3, 0]]
[rowsums, colsums, problem]
end
#
# From http://en.wikipedia.org/wiki/Survo_Puzzle, second example
# difficulty 0
def puzzle(2) do
rowsums = [9, 12]
colsums = [9, 7, 5]
problem = [[0, 0, 3], [0, 6, 0]]
[rowsums, colsums, problem]
end
# http://en.wikipedia.org/wiki/Survo_Puzzle, third example
# difficulty 150 ("open puzzle", i.e. no hints}
# It's an unique solution.
# (817 propagations with Gecode/fz, and 33 failures, 88 commits}
# r = 3;
# c = 4;
# rowsums = [24,15,39];
# colsums = [21,10,18,29];
# matrix = array2d(1..r, 1..c,
# [
# 0, 0, 0, 0,
# 0, 0, 0, 0,
# 0, 0, 0, 0
# ]};
# Note: this version has no hints
def puzzle(3) do
rowsums = [24, 15, 39]
colsums = [21, 10, 18, 29]
problem = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
[rowsums, colsums, problem]
end
# same as above but with hints: difficulty 0
# (15 propagations with Gecode/fz, no failures, no commits]
# matrix = array2d(1..r, 1..c,
# [
# 7, 0, 5, 0,
# 0, 1, 0, 8,
# 0, 0, 11, 0
# ]];
def puzzle(4) do
rowsums = [24, 15, 39]
colsums = [21, 10, 18, 29]
problem = [[7, 0, 5, 0], [0, 1, 0, 8], [0, 0, 11, 0]]
[rowsums, colsums, problem]
end
# http://www.survo.fi/puzzles/280708.txt, third puzzle
# Survo puzzle 128/2008 (1700] #364-35846
#
# A B C D E F
# 1 * * * * * * 30
# 2 * * 18 * * * 86
# 3 * * * * * * 55
# 22 11 42 32 27 37
#
# Solution:
# 4 1 10 5 3 7 = 30
# 12 8 18 16 15 17 = 86
# 6 2 14 11 9 13 = 55
# 22 11 42 32 27 37
#
def puzzle(5) do
rowsums = [30, 86, 55]
colsums = [22, 11, 42, 32, 27, 37]
problem = [[0, 0, 0, 0, 0, 0], [0, 0, 18, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
[rowsums, colsums, problem]
end
#
# http://en.wikipedia.org/wiki/Survo_Puzzle, under "Swapping method"
# (open puzzle]
#
# Solution:
# 15 16 12 8 = 51
# 14 11 7 4 = 36
# 13 10 6 3 = 32
# 9 5 1 2 = 17
# 51 42 26 17
def puzzle(6) do
rowsums = [51, 36, 32, 17]
colsums = [51, 42, 26, 17]
problem = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
[rowsums, colsums, problem]
end
def main() do
for p <- 1..6 do
IO.puts("\nPuzzle ##{p}")
[rowsums, colsums, problem] = puzzle(p)
survo_puzzle(rowsums, colsums, problem)
end
end
def survo_puzzle(rowsums, colsums, problem) do
rows = length(rowsums)
cols = length(colsums)
dom = 1..(rows * cols)
# Decision variables
x =
for i <- 0..(rows - 1) do
for j <- 0..(cols - 1) do
v = CPUtils.mat_at(problem, i, j)
if v > 0 do
# > 0: this is a hing
IntVariable.new(v, name: "x[#{i},#{j}]")
else
# 0: unknown
IntVariable.new(dom, name: "x[#{i},#{j}]")
end
end
end
x_flatten = x |> List.flatten()
#
# Constraints
#
all_different_constraint = AllDifferent.new(x_flatten)
# Row constraints
row_constraints =
for {s, row} <- Enum.zip(rowsums, x) do
Sum.new(s, row)
end
# Column constraints
col_constraints =
for {s, col} <- Enum.zip(colsums, CPUtils.transpose(x)) do
Sum.new(s, col)
end
constraints = [all_different_constraint] ++ row_constraints ++ col_constraints
model =
Model.new(
x_flatten,
constraints
)
Logger.configure(level: :info)
{:ok, result} =
CPSolver.solve_sync(model,
search: {:first_fail, :indomain_min},
# stop_on: {:max_solutions, 1},
timeout: 30_000,
space_threads: 12
)
# IO.inspect(result.solutions)
# IO.inspect(Enum.map(result.solutions, fn sol -> Enum.zip(result.variables, sol) end))
IO.inspect(result.statistics)
result.solutions
|> Enum.map(fn s -> s |> Enum.take(rows * cols) end)
|> Enum.map(fn s -> print_solution(s, rows, cols, rowsums, colsums) end)
# result.statistics
end
end