# # 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/ # ## Boris Okner: modified to sync with the latest API, ## change naming and result handling. ## defmodule CPSolver.Examples.Hakank.SurvoPuzzle do # import Enum # Conflicts with CPSolver.Constraint.Factory.sum import Hakank.CPUtils alias CPSolver.IntVariable alias CPSolver.Constraint.Sum # alias CPSolver.Constraint.Equal alias CPSolver.Constraint.AllDifferent.FWC, as: AllDifferent alias CPSolver.Model # import CPSolver.Constraint.Factory # import CPSolver.Variable.View.Factory 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 problem = [[0, 6, 0, 0], # 0 means unknown [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 run() 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 = mat_at(problem,i,j) if v > 0 do # > 0: this is a hint 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, 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(model, search: {:first_fail, :indomain_min}, # stop_on: {:max_solutions, 1}, timeout: :infinity, 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