Wolfram Blog
Paritosh Mokhasi

使用整数优化建设和解决Wolfram语言的数独游戏manbet万博app

2020年6月2日 -Paritosh Mokhasi,内核开发,算法R&d

使用整数优化建设和解决Wolfram语言的数独游戏manbet万博app

数独是一种流行的游戏是推动玩家的分析,数学和心理能力。解决数独问题长期以来一直在讨论manbet万博app沃尔弗拉姆社区,和there has been一些精彩的代码提出了解决数独问题。要添加到讨论中,我将演示几个特点,是新的数学12.1版包括本场比赛如何解决与使用功能的整数优化问题LinearOptimization,以及如何生成新的数独游戏。

解决数独编程

In a typical sudoku game, the player is presented with a 9×9 grid/board with some numbers exposed in certain positions of the board.

This is an example of a standard sudoku board:

Standard sudoku board

玩家是为了填补1和9之间用数字的空点toif it’s an板)在董事会下面三个规则:

1.每一行必须包含所有的数字1-9。

2.每一列必须包含所有的数字1-9。

3.每个3×3块(显示为灰色或白色块)必须包含所有数字1-9。

应用这些三条规则,玩家现在必须填写板上,使得没有任何规则被违反。

我将利用SparseArray代表最初的数独谜题,构建了“数独游戏” example forLinearOptimization

initialSudokuBoard = SparseArray
&#10005

initialSudokuBoard = SparseArray [{{1,3}  - > 5,{1,4}  - > 3,{2,1}  - > 8,{2,8}  - > 2,{3,2}  - >如图7所示,{3,5}  - > 1,{3,7}  - > 5,{4,1}  - > 4,{4,6}  - > 5,{4,7}  - > 3,{5,2}- > 1,{5,5}  - >如图7所示,{5,9}  - > 6,{6,3}  - > 3,{6,4}  - > 2,{6,8}  - > 8,{7,2}  - > 6,{7,4}  - > 5,{7,9}  - > 9,{8,3}  - >图4,{8,8}  - > 3,{9,6}  -> 9,{9,7}  - > 7},{9,9},_];ResourceFunction [ “DisplaySudokuPuzzle”] [initialSudokuBoard]

为了解决这个问题作为一个整数优化问题,让对于元件的可变。让是该元素of vector。什么时候, 然后holds the number。Each只包含一个号码,这样只能包含一个非零元素,即

明确
&#10005

明确[z]; squareConstraints = Table[{Total[z[i, j]] == 1, 0 \[VectorLessEqual] z[i, j] \[VectorLessEqual] 1, z[i, j] \[Element] Vectors[9, Integers]}, {i, 9}, {j, 9}];

将第一数独的规则,每一行都必须包含所有的数字,即,其中是那些的一个九维向量:

onesVector = ConstantArray
&#10005

onesVector = ConstantArray[1, 9]; rowConstraints = Table[Sum[z[i, j], {j, 9}] == onesVector, {i, 9}];

The second rule says that each column must contain all the numbers, i.e.

columnConstraints = Table
&#10005

columnConstraints = Table[Sum[z[i, j], {i, 9}] == onesVector, {j, 9}];

第三个规则说,每3×3块必须包含所有数字,即

blockConstraints = Table
&#10005

blockConstraints =表[萨姆[Z [I + M,J + N],{M,3},{N,3}] == onesVector,{I,{0,3,6}},{Ĵ,{0,3,6}}];

Collectively, these make the sudoku constraints for any puzzle:

sudokuConstraints = {squareConstraints,rowConstraints,columnConstraints,blockConstraints};
&#10005

sudokuConstraints = {squareConstraints,rowConstraints,columnConstraints,blockConstraints};

Collect all the variables:

vars = Flatten
&#10005

瓦尔=平铺[表[Z [I,J],{I,9},{Ĵ,9}]];

Convert the known values into constraints. If element持有数量, 然后

knownConstraints = MapThread
&#10005

knownConstraints = MapThread [索引[Z @@#1,#2] == 1&{initialSudokuBoard [ “NonzeroPositions”],initialSudokuBoard [ “NonzeroValues”]}];

LinearOptimization通常用于以最小化线性目标对象的一组线性约束。在这种情况下,目的是0,因为有比在一个可行的办法兴趣没有客观其他:

res = LinearOptimization
&#10005

RES = LinearOptimization [0,{sudokuConstraints,knownConstraints},乏];短[RES,3]

知道哪些数进入该位置,该信息必须从载体中提取。这是很容易做到的:

短
&#10005

短[pos = MapThread[List @@ #1 -> Range[9].#2 &, {vars, vars /. res}], 4]

由先前的输出转换成可视化的结果SparseArray

ResourceFunction
&#10005

ResourceFunction [ “DisplaySudokuPuzzle”] [SparseArray [POS]]

正如你所看到的,把问题一起,解决花了6-7行代码。该过程已被放置为ResourceFunctionSolveSudokuPuzzlethat users can call to solve a sudoku puzzle:

ResourceFunction
&#10005

ResourceFunction["SolveSudokuPuzzle"][initialSudokuBoard]

This function has been made quite general and has the capacity to solve sudoku puzzles of arbitrary size. The solver also accepts negative numbers being present on the board. If a negative number exists, then the solver tries to solve the puzzle with the assumption that the number at that position cannot exist.

生成数独谜题

我们将用它来生成一个数独题的策略是先从一个完整的电路板。由此看来,一个元件将被随机选择的,并且在于在该元素将被删除的数量。然后,我们将执行的条件,我们从该元件上取下数量不能撒谎那个元素。如果解算回来了,尽管附加条件的方案,这意味着该位置的数量不是唯一的,不能离开董事会。如果解算回来了失败的结果,那么这个数字在该位置上是独一无二的,可以拆卸。

为实施这一战略,需要有以生成完整的随机数独板的方式。有几种方法,人们可以用它来生成一个完整的数独板。一种方法是随机指定的数独板的对角线项,并允许解算器为我们生成一个难题:

fullSudokuPuzzle = ResourceFunction
&#10005

fullSudokuPuzzle = ResourceFunction [ “SolveSudokuPuzzle”] [SparseArray @对角矩阵[RandomSample [范围[9]]]];ResourceFunction [ “DisplaySudokuPuzzle”] [fullSudokuPuzzle]

这将产生三个十万可能的难题。一个优点我们的求解器是,我们还可以指定某些号码不能出现在特定的位置。这是通过这个数字负在该位置进行。采取这一功能的优势,超过一级亿的难题可以通过修改程序产生:

initialPuzzle = SparseArray @对角矩阵
&#10005

initialPuzzle = SparseArray @对角矩阵[ RandomSample[Range[9]]*RandomChoice[{1, -1}, 9]]; refSudokuMat = ResourceFunction["SolveSudokuPuzzle"][initialPuzzle]; ResourceFunction["DisplaySudokuPuzzle"][refSudokuMat]

当然,这仍然是可能的总板的一个非常小的一部分,但它是一个开始。

现在,我们有一个完整的主板,让我们假设我们想保持从板只有50元。迭代代码如下:

minElementsToKeep = 50; sudokuElements = RandomSample
&#10005

minElementsToKeep = 50;sudokuElements = RandomSample [线程[refSudokuMat [ “NonzeroPositions”]  - > refSudokuMat [ “NonzeroValues”]]];N = 81;I = 1;虽然[长度[sudokuElements]> minElementsToKeep &&我
                  

注意额外的条件,其中通过使这些数字负片除去不能在某些位置出现的数字。现在,我们可以展示我们的新出炉的数独谜题:

sudokuPuzzle = SparseArray
&#10005

sudokuPuzzle = SparseArray [sudokuElements,{9,9},_];ResourceFunction [ “DisplaySudokuPuzzle”] [sudokuPuzzle]

这是可能的仔细检查这一难题是可以解决的,我们回来的结果是一样的基准数独,我们开始:

ResourceFunction
&#10005

ResourceFunction [ “DisplaySudokuPuzzle”] [#] / @ {refSudokuMat,ResourceFunction [ “SolveSudokuPuzzle”] [sudokuPuzzle]}

请注意,解决谜题回收参考谜。

一个ResourceFunctionGenerateSudokuPuzzle已经开发了用户的方便,将产生不同大小的数独游戏,并确定有多少元素需要进行曝光:

{全膳食,sudokuPuzzle} = ResourceFunction
&#10005

{全膳食,sudokuPuzzle} = ResourceFunction [ “GenerateSudokuPuzzle”] [3,0.4]

ResourceFunction
&#10005

ResourceFunction [ “DisplaySudokuPuzzle”] [#] / @ {全膳食,sudokuPuzzle}

由于该函数的一般性质,可以以不同的尺寸来产生数独板。这里是一个4×4板:

{全膳食,sudokuPuzzle} = ResourceFunction
&#10005

{全膳食,sudokuPuzzle} = ResourceFunction["GenerateSudokuPuzzle"][2, 0.5]; ResourceFunction["DisplaySudokuPuzzle"][#] & /@ {fullBoard, sudokuPuzzle}

Next is a 16×16 board. The computation time to generate boards increases considerably with size because there are now 256 binary vectors of length 16 (as opposed to 81 vectors of length 9 for the 9×9 case). The following one took about 30 seconds to generate (but will change for every run):

{全膳食,sudokuPuzzle} = ResourceFunction
&#10005

{全膳食,sudokuPuzzle} = ResourceFunction["GenerateSudokuPuzzle"][4, 0.6]; ResourceFunction["DisplaySudokuPuzzle"][#] & /@ {fullBoard, sudokuPuzzle}

我会实话实说:我没有解决这个数独题的勇气。我很想听听您的意见,如果你试图解决这些大难题之一!

确定难度级别

狂热的玩家可能会问下一个明显的问题:“什么是以前的谜题的难度”这是回答一个棘手的问题,我认为这是主观的。然而,我们可以尝试排名1到10之间的产生的谜题,其中1为方便和10是很辛苦,通过查看该板的许多位置如何可以有自己的元素,通过使用三个规则,逐步填充板唯一标识直到没有独特的元素存在。

因此,对于一个数独题暴露元素的40%,难度将是:

{全膳食,sudokuPuzzle} = ResourceFunction
&#10005

{全膳食,sudokuPuzzle} = ResourceFunction [ “GenerateSudokuPuzzle”] [3,0.4]。ResourceFunction [ “EstimateSudokuDifficultyLevel”] [sudokuPuzzle]

你可以生成一个难题通过允许难题generator to return its hardest possible puzzle by specifying the number of exposed elements to be 0. Of course, that will not be possible, so the generator will return its best puzzle that can be solved uniquely:

{全膳食,sudokuPuzzle} = ResourceFunction
&#10005

{全膳食,sudokuPuzzle} = ResourceFunction [ “GenerateSudokuPuzzle”] [3,0];ResourceFunction [ “EstimateSudokuDifficultyLevel”] [sudokuPuzzle]

当然,每一次运行将产生不同数量和谜题。这是硬的难题,发电机返回:

ResourceFunction
&#10005

ResourceFunction [ “DisplaySudokuPuzzle”] [sudokuPuzzle]

Solving Killer Sudoku

The killer sudoku game is a variant of the original. It follows the same three rules of the original game, but instead of having numbers specified at certain positions, the player is provided with a board that looks like this:

杀手数独板

每个颜色组被称为“笼”,并提供了用于每笼的数。这个数字代表在笼子里的所有数字的总和。例如,左上保持架包含数字26和由四个红色正方形。这意味着,在这四个红色正方形的总数目的必须等于26。

在我们的框架,这其实是非常容易做到的。在使用解决了杀手数独谜题诀窍LinearOptimization是将每个二元载体的关联另一个变数实际上包含在该位置的数量。这是通过添加下面的一组约束,除了数独解算器的限制来完成:

短
&#10005

短[Table[Indexed[y, {i, j}] == Range[9].z[i, j], {i, 9}, {j, 9}], 2]

有一个ResourceFunctionSolveKillerSudokuPuzzle中集成了更多的约束和解决提供谜。

生成杀手数独板

当然,也仍然需要创建杀手数独板的方式。我的方法是随机生成的俄罗斯方块块状图案,然后使用MorphologicalComponents提取各个块(我渴望从他们创造性的方法来产生一个杀手数独谜题倾听读者的意见)。这种方法我概述生命作为ResourceFunctionGenerateKillerSudokuPuzzle使我们能够生成一个杀手数独谜题所需的信息:

短
&#10005

短[{refSudokuBoard,{cagePos,cageVals}} = ResourceFunction [ “GenerateKillerSudokuPuzzle”] [],4]

这将有助于可视化这一难题,这是可以做到用DisplayKillerSudokuPuzzle

ResourceFunction
&#10005

ResourceFunction [ “DisplayKillerSudokuPuzzle”] [cagePos,cageVals]

我要指出的是,生成的杀手数独谜题实际上是更容易和更便宜的产生比传统的数独题,因为没有内容删除。这个难题是从以下参考数独板产生:

ResourceFunction
&#10005

ResourceFunction [ “DisplaySudokuPuzzle”] [refSudokuBoard]

You can manually check that the puzzle is valid by adding the numbers in the cages. Our killer sudoku puzzle can now be solved:

solvedPuzzle = ResourceFunction
&#10005

solvedPuzzle = ResourceFunction [ “SolveKillerSudokuPuzzle”] [cagePos,cageVals]

在实验期间,我发现有时整数优化问题在几秒钟之内解决,有时需要30秒以上。因此,它是难以割舍的问题是如何能很快地解决了一个很好的估计。下面是这种特殊情况下的结果:

ResourceFunction
&#10005

ResourceFunction["DisplaySudokuPuzzle"][solvedPuzzle]

我也注意到,有时解决的难题将不匹配的参考数独板。这一点,在我看来,是完全没问题。根据我的经验,较大的笼大小,更大的灵活性,在求解器获得可行的解决方案,而且数量,因此,可以四处走动。小笼子,而另一方面,使问题更加严格。

一个dditional Optimization Tools

我希望我提供你一个短暂的一瞥到优化,特别是(混合)整数最优化的世界,并优化框架如何可以用来解决一些有趣的问题。有大量的应用实例,你可以在文档页面找到LinearOptimizationQuadradicOptimizationSecondOrderConeOptimizationSemidefiniteOptimizationConicOptimization

You will surely have fun playing and creating your own killer sudoku games. I tried solving a hard one from the web, and after an hour of yelling at the paper, I realized it is just easier for the computer to do it, and, well, here we are. Feel free to share your best puzzles in the comments below, or join the conversation on Wolfram Community.

可以完全访问最新的Wolfram语言的功能与manbet万博app数学12.1要么Wolfram|One审判。

发表评论

没意见




发表评论