BPM 观点:使用云中的业务规则来解决数独,第 1 部分: 实现规则应用程序

本系列文章介绍了一种组合使用业务规则和启发式深度优先搜索来解决数独 (Sudoku) 谜题的混合方法。这种方法模拟了人们解决数独时使用的方法。使用 WebSphere® Operational Decision Management 构建的规则应用了正向链逻辑;当无法通过这种逻辑找到答案时,启发式搜索和回溯法就会模拟试错法 (trial-and-error)。在本系列的第 2 部分中,我们将介绍用于将规则集嵌入到由公共云托管的 Web 应用程序中的架构和流程。 本文来自于 IBM Business Process Management Journal 中文版

Rajesh Rao, BRMS IT Specialist, IBM

Rajesh (Raj) Rao has been working in the area of expert systems and business rule management systems for over 20 years, during which time he has applied business rules technology to build diagnostic, scheduling, qualification, and configuration applications across various domains such as manufacturing, transportation, and finance. He has been with IBM since 2009. With a background in Artificial Intelligence, his interests include Natural Language Processing and Semantic Web.



Raj Rao, 决策管理高级解决方案架构师, IBM

Rajesh (Raj) Rao在专家系统和业务规则管理系统方面有 20 多年的工作经验,在这期间,他应用业务规则技术跨越各个领域,比如制造、运输和金融业,来构建诊断、日程安排、资格证书和配置应用程序。他在 IBM 工作已经快 2 年了。他有 Artificial Intelligence 背景,感兴趣的领域包括 Natural Language Processing 和 Semantic Web。



2013 年 5 月 02 日

开始之前

本专栏面向 WebSphere Operational Decision Management 开发人员,并假设读者能够从开发人员的角度较好地理解该产品。参阅 参考资料 部分,了解完成本专栏介绍的步骤需要具备的知识。在撰写本专栏时,我使用的产品版本为 WebSphere Operational Decision Management V7.5,特别是 Rule Designer。

本文将解释规则如何有效、简明、隐式迭代数独网格中的行和列,具有编程背景的开发新手能够从中获益良多。对于级别更高的开发人员,本文将讨论如何为了提高效率而进行规则任务划分,如何使用规则作为混合方法的一部分,以及哪些规则在其他约束满足问题上具有更广泛的适应性。


简介

几年前我去探望我的爷爷,我的几个淘气的表兄弟最喜欢在爷爷家玩耍,我本以为他们会玩板球游戏或其他什么游戏,结果发现所有人都蜷缩在屋子里,聚精会神地在纸上写着什么,仿佛正在参加一场紧张的考试,后来我发现原来他们都在玩数独游戏。这是我第一次接触如今已经风靡一时的数独游戏。为了充分介绍这款游戏,我必须承认时至今日我也是它的一名狂热粉丝。

数独游戏(如 图 1 所示)由 9 行 9 列组成,总共 81 个单元格,它的目标很简单:用 1 到 9 之间的数字填满这些方格,使每行每列以及每 3x3 个方格区域内包含 1 到 9 之间的所有数字;这意味着每个行、每个列或每个方格块内不能有重复数字。游戏在开始时已经提供了一些值,剩下的空格将由玩游戏的人填满。

图 1. 数独游戏
数独游戏

网格中的每个空格都由它所在的行和列标识。例如,方格位于第 9 行第 3 列,那么它被写为 r9c3

经典的 9×9 数独求解网格的数量非常庞大,大概有 6.67×1021 个,而解决数独游戏的常见问题就是 NP-complete 结构。玩数独游戏的乐趣来自于通过一些逻辑步骤从众多答案中找出正确的答案,这些步骤都令您逐渐接近正确的答案。每个逻辑步骤都需要从已经填充的方格中找到规律。填写一个方格后会限制其他空格的填写/这种正向链方法非常适合使用正向链规则引擎进行模拟,比如 WebSphere Operational Decision Management,其中新知识的断言会触发所有相关的规则,因此将会通过分步的方式从初始状态到达目标状态。

然而,有时候人们在玩数独的时候会陷入死胡同,无法前进一步。此时,他们需要求助于 “试错法”,他们必须猜测某个方格的值,并且可能需要回过头来修改这个猜测的值以及根据这个值推测出的其他所有值。需要猜测的次数与正向链使用的知识库中的知识数量成反比。这种试错法是否有效取决于玩游戏的人选择哪一个空格进行猜测。

在构建样例规则应用程序时,会在传统 AI 中模拟这种方法。规则应用程序的目标是:

  1. 使用数独玩家常用的启发式方法。因此,我们避开了数独中更晦涩难懂的启发式方法,比如 X-Wings 和 Swordfish。
  2. 将试错法作为最后一种选择。
  3. 提供一个分步说明的工具解释为什么要按特定的方式填充某个数独方格。

注意:可以使用多种方法来解决数独,因此本专栏并没有暗示基于规则的方法是惟一方法或最佳方法。例如,可以使用约束编程表示法来解决数独(并使用 IBM® ILOG Operational Decision Manager Enterprise 平台),这种做法非常简洁明了。


规则实现

业务规则可以有效地捕捉人们用于解决数独的启发式知识。本节将概括介绍这种知识和相应的业务规则实现。

9 种启发性知识

对 Bob Hanson 的 Sudoku Assistant 进行改编后,我们定义了 9 种可以用业务规则实现的可用来解决数独谜题的启发式方法。除了行、列和方格块外,这些启发式方法被称为候选值,即可以填充到某个方格的可能值。因此,candidate k 就是指方格中的可能值 kk 是介于 1 到 9 之间的数。

  • 启发式方法 1:排除式启发

    如果某个方格的值为 k,那么同一行、同一列或同一方格块中其他方格的值不得为 k。

  • 启发式方法 2:显性惟一解 (naked single) 启发

    如果候选值在某个方格中可行,并且其他候选值都不能填在此方格中,那么这个方格必为 k。

  • 启发式方法 3:隐性惟一解 (hidden single) 启发

    如果候选值 k 只能填在某个行、列或方格块中的某个方格,那么该方格的值必为 k。

  • 启发式方法 4:行/列支锁定候选值的启发

    如果某个候选值适合填入到某个行/列和方格块的交叉处,但是不适合该行/列的其他位置,那么它也不适合填入这个方格块的其他位置。

  • 启发式方法 5:方格块中锁定候选值的启发

    如果某个候选值适合填入到某个行/列和方格块的交叉处,但是不适合该方格块内的其他位置,那么它不适合填入这一行/列的其他位置。

  • 启发式方法 6:显性数偶 (naked pair) 的启发

    如果两个候选值适合某个给定行/列的两个方格,并且没有其他候选值适合填入这两个方格,那么这两个方格也不适合填入该行/列支的其他位置。

  • 启发式方法 7:隐性数偶 (hidden pair) 的启发

    如果两个候选值适合某个给定行/列的两个方格,并且这两个方格也不适合填入该行/列支的其他位置,那么没有其他候选值适合填入这两个方格。

  • 启发式方法 8:显性三元组 (naked trio) 的启发

    如果三个候选值适合某个方格块的三个方格,并且没有其他候选值适合填入这三个方格,那么这三个方格也不适合填入该方格块的其他位置。
  • 启发式方法 9:隐性三元组 (hidden trio) 的启发

    如果三个候选值适合某个方格块的三个方格,并且这三个方格也不适合填入该方格块的其他位置,那么没有其他候选值适合填入这三个方格。

业务对象模型 (BOM)

在构建应用这 9 种启发式方法的规则之前,需要定义数据结构,这些数据结构构成了定义规则所需的词汇表。对前面小节介绍的启发式方法进行分析后可以发现一些关键的类:

  1. 数独网格:这是一个由整数组成的二维数组,表示一个数独面板。
  2. 候选方格值:该对象表示某个方格的潜在的候选值。
  3. 求解后的方格值:该对象表示一个求解后的方格,方格有一个分配给它的确定的值。该对象有一个分配类型,用于确定分配值使用的规则。

图 2 的类图中显示了这些对象。名为 Sudoku 的类是一个高级容器类,表示规则引擎的输入和输出。如前所述,除了分析方格值之外,规则引擎还负责提供解释工具。在执行规则期间,所有重要消息都被添加到数独容器对象中。这些重要消息可能是临时消息或方格求解事件。所有重要事件都将捕捉到这个消息列表中。

如果规则未找到解决方法,那么它会找出最合适的空格进行猜测,尝试使用试错法。这个过程被封装到了下面的 SudokuOpenCell 类中。注意,数独容器有一个 mostPromisingCell 属性,类型为 SudokuOpenCell,它将由规则引擎在必要时进行设置。

图 2. Execution Object Model
Execution Object Model

查看图 2 的大图。)

规则表示

可以使用两种基本规则来解决数独:排除可能的候选值并向某个方格分配一个数字。这些规则假设所有合适的候选值和已求解的方格值位于内存存储器中。排除规则从内存存储器中去掉候选值,而分配规则向内存存储器中插入一个已求解的方格值,并更新数独网格。在规则引擎中使用内存存储器可以使 Rete 网络能够感知到数独网格的变化并在出现变化时相应地触发其他规则。

分配规则

前面介绍的显性惟一解启发和隐性惟一解启发是仅有的两种分配规则。它们的实现非常简单。例如,下面所示的显性惟一解启发向一个方格分配了一个值,这个值是这个方格块的惟一候选值。第 4 行到第 7 行中的规则条件将检查方格块中的某个值只有一个候选值。如果满足该条件,那么第 9 行和第 10 行中的操作将撤回候选值,为方格分配一个值,创建一个已求解的方格对象,并将其插入到内存存储器。

1. definitions 
2.	set 'candidateCell' to a candidate cell value  ; 
3. if
4.	the number of candidate cell values 
5.	  where the block of each candidate cell value equals the block of candidateCell 
6.	  and the value of each candidate cell value  equals the value of candidateCell , 
7. 	equals 1   
8. then 
9.	retract candidateCell ; 
10.	  insert object : create a resolved cell value using candidateCell , 
assignment type: ONLY_POSSIBLE_VAL_IN_BLOCK ;

该规则将检查方格块中的显性惟一解,并添加这个规则的其他版本,检查行和列中的惟一解。

隐性惟一解启发将查看某个方格是否有惟一候选值,它由下面清单所示的规则实现。

1. definitions 
2.	set 'candidateCell' to a candidate cell value ; 
3. if
4.	the number of candidate cell values 
5.	  where the row of each candidate cell value equals the row of candidateCell 
6.	  and the col of each candidate cell value equals the col of candidateCell , 
7. 	equals 1   
8. then
9.	retract candidateCell ; 
10.	insert object : create a resolved cell value using candidateCell , 
assignment type: ONLY_POSSIBLE_VAL_IN_CELL ;

排除规则

排除规则将去掉可能的候选值,可以将复杂性归类为从简单到非常复杂。在比较简单的类别中,我们使用排除式启发,从已求解的方格所在的行、列和方格块中排除候选值。下面显示了应用于行的排除式启发,类似的规则可以从列和方格块中排除候选值。

definitions 
	set 'resolvedCell' to a resolved cell value ; 
	set 'candidateCell' to a candidate cell value 
		where the row of this candidate cell value  equals the row of 
resolvedCell 
		and the value of this candidate cell value  equals the value of 
resolvedCell ; 
then 
	retract candidateCell ;

比较复杂的规则与锁定候选值有关的一些规则。例如,块状态下的锁定候选值的启发:

If a candidate k is possible in a certain intersection of
row and block but is not possible elsewhere in that block,
then it is also not possible elsewhere in that row.

重申一下,如果某个方格块而不是某个行中没有候选值(即候选值被锁定到该行中的这个块),那么可以从该行中的其他方格中排除该候选值。下面的清单显示了规则的实现。第 3-6 行绑定了在满足第 8-12 行时需要排除的行中的其他候选值。在规则操作(第 14 行)中,我们将这种排除行为作为重要事件添加,从而作为解释工具的一部分显示。

1. definitions 
2.	set 'candidateCell1_val1' to a candidate cell value ;
3.	set 'othercandidateCellValue' to a candidate cell value 
4.	  where the block of this candidate cell value is not the block of 
candidateCell1_val1 
5.	  and the row of this candidate cell value is the row of candidateCell1_val1 
6.	  and the value of this candidate cell value is the value of 
candidateCell1_val1 ;  
7. if
8.	the number of candidate cell values 
9.	  where the row of each candidate cell value is not the row of 
candidateCell1_val1 
10.		and the value of each candidate cell value is the value of 
candidateCell1_val1 
11.		and the block of each candidate cell value is the block of 
candidateCell1_val1 , 
12.	    equals 0
13. then 
14.	add "Found locked cell value of " + the value of candidateCell1_val1  + " 
in block " 
+ the block of candidateCell1_val1 + ". Removing this value from the row " +  
the row 
of othercandidateCellValue  to the messages of 'the sudoku' ;
15.	retract othercandidateCellValue  ;

更复杂的规则是与显性数偶和隐性数偶有关的规则。为了唤醒您的记忆,下面显示了显性数偶启发:

If 2 candidates are possible in a set of 2 cells of a given row,
and no other candidates are possible in those 2 cells, then those 2
candidates are not possible elsewhere in that row.

规则实现如下面的清单所示。Cell1cell2 是具有显性数偶的两个方格。定义部分的第 7 行确保这两个方格位于同一行中。每个方格只有两个候选值,第 17-25 行中的规则条件对这一点进行检查。第 13-15 行中绑定的所有其他候选值将被排除并从内存存储器(行 28)中撤回。

1. definitions 
2.	set 'cell1_candidate1' to a candidate cell value ;
3.	set 'cell1_candidate2' to a candidate cell value 
4.	  where the row of this candidate cell value is the row of cell1_candidate1 
5.	     and the col of this candidate cell value is the col of cell1_candidate1 ; 
6.	set 'cell2_candidate1' to a candidate cell value 
7.	  where the row of this candidate cell value is the row of cell1_candidate1 
8.	     and the value of this candidate cell value is the value of cell1_candidate1 ;
9.	set 'cell2_candidate2' to a candidate cell value 
10.	   where the row of this candidate cell value is the row of cell2_candidate1
11.	     and the col of this candidate cell value is the col of cell2_candidate1
12. 	     and the value of this candidate cell value is the value of 
cell1_candidate2 ; 
13.	set 'otherCandidate' to a candidate cell value 
14.	   where the row of this candidate cell value is the row of cell1_candidate1 
15.	     and the value of this candidate cell value is the value of 
cell1_candidate1 ;  
16. if
17.	the number of candidate cell values 
18.	   where the row of each candidate cell value is the row of cell1_candidate1
19.	     and the col of each candidate cell value is the col of cell1_candidate1 , 
20.	   equals 2
21.	and 
22.	the number of candidate cell values 
23.	  where the row of each candidate cell value is the row of cell2_candidate1
24.	     and the col of each candidate cell value is the col of cell2_candidate1 , 
25.	equals 2
26. then 
27.	add "Rule - naked pair: Removing " + the value of otherCandidate   + " from row " 
+ the row of otherCandidate  to the messages of 'the sudoku' ;
28.	retract otherCandidate  ;

最复杂的规则与三元组有关。隐性三元组规则声明,当三个候选值适合同一方格块内的三个方格,并且这三个候选值不能填充到该方格块内地其他位置,那么其他任何候选值都不能填充到这些方格中。

下面的清单显示了这个规则的实现。在这条规则中,我们绑定了三个候选值,每个候选值针对三个方格,因此总共 9 个绑定,如第 2-30 行所示。规则条件(第 36-52 行)确保这三个方格的每个方格只有三个候选值,这意味着除了定义部分已经绑定的候选值之外,不存在其他候选值。在满足这个条件后,cell1 的其他所有候选值(第 31 行中绑定)将从内存存储器中移除(第 55 行)。请注意,我们并不需要显式地从 cell2cell3 中删除候选值,因为在再次激活这条规则时,Rete 算法会确保该规则匹配这些方格,正如在 cell1 中所做的那样。

1. definitions 
2.	set 'cell1_candidate1' to a candidate cell value ;
3.	set 'cell1_candidate2' to a candidate cell value 
4.	  where the row of this candidate cell value is the row of cell1_candidate1 
5.	      and the col of this candidate cell value is the col of 
cell1_candidate1 ; 
6.	set 'cell1_candidate3' to a candidate cell value 
7.	  where the row of this candidate cell value is the row of cell1_candidate1 
8.	      and the col of this candidate cell value is the col of 
cell1_candidate1 ; 
9.	set 'cell2_candidate1' to a candidate cell value 
10.	  where the block of this candidate cell value is the block of cell1_candidate1 
11.	      and the value of this candidate cell value is the value of 
cell1_candidate1 ;
12.	set 'cell2_candidate2' to a candidate cell value 
13.	  where the row of this candidate cell value is the row of cell2_candidate1
14.	      and the col of this candidate cell value is the col of cell2_candidate1
1	      and the value of this candidate cell value is the value of 
cell1_candidate2 ; 
16.	set 'cell2_candidate3' to a candidate cell value 
17.	  where the row of this candidate cell value is the row of cell2_candidate1
18.	      and the col of this candidate cell value is the col of cell2_candidate1
19.	      and the value of this candidate cell value is the value of 
cell1_candidate3 ; 
20.	set 'cell3_candidate1' to a candidate cell value 
21.	  where the block of this candidate cell value is the block of cell1_candidate1 
22.	      and the value of this candidate cell value is the value of 
cell1_candidate1 ;
23.	set 'cell3_candidate2' to a candidate cell value 
24.	  where the row of this candidate cell value is the row of cell3_candidate1
25.	      and the col of this candidate cell value is the col of cell3_candidate1
26.	      and the value of this candidate cell value is the value of 
cell1_candidate2 ; 
27.	set 'cell3_candidate3' to a candidate cell value 
28.	  where the row of this candidate cell value is the row of cell3_candidate1
29.	      and the col of this candidate cell value is the col of cell3_candidate1
30.	      and the value of this candidate cell value is the value of 
cell1_candidate3 ; 
31.	set 'otherCandidate' to a candidate cell value 
32.	  where 
33.	    the row of this candidate cell value is the row of cell1_candidate1 
34.	      and the col of this candidate cell value is the col of cell1_candidate1 ;  
35. if
36.	the number of candidate cell values 
37.	  where 
38.	    the block of each candidate cell value is the block of cell1_candidate1 
39.	    and the value of each candidate cell value is the value of 
cell1_candidate1 , 
40.	  equals 3
41. and 
42. the number of candidate cell values 
43.	  where 
44.	    the block of each candidate cell value is the block of cell1_candidate2
45.         and the value of each candidate cell value is the value of 
cell1_candidate2 , 
46.	    equals 3
47. and 
48. the number of candidate cell values 
49. 	   where 
50.	    the block of each candidate cell value is the block of cell1_candidate3
51.	    and the value of each candidate cell value is the value of cell1_candidate3 , 
52.	    equals 3
53. then 
54.	   add "Rule - hidden trio: Removing " + the value of otherCandidate   + 
" from block " 
+ the block of otherCandidate  to the messages of 'the sudoku' ;
55.	   retract otherCandidate  ;

该规则只捕捉最理想的隐性三元组,其中所有三个候选值都填充在三个方格中。然而,即使其中一个方格只有两个候选值,仍然可以将它作为隐性三元组。考虑到实现这些复杂规则的性能成本,我们决定不实现用于处理不完整三元组的规则。这一策略也适用于更高阶的子集。例如,对于非常少见的配置,实现可处理 “四元组”(在四个方格中)的规则的性能成本会非常高。对于这些极少数的情况,试错法更加合适。

猜测规则

如果采用逻辑应用规则后未找到解决办法,那么求解器需要尝试使用试错法。试错法是否有效很大程度上取决于选择哪个方格进行猜测。规则应用了一种简单的启发方法进行选择 —— 选择候选值最少的空格。

猜测规则执行以下步骤来找出最理想的方格:

  1. 创建空白的方格并插入到内存存储器中。
  2. 向这些空方格添加候选值。
  3. 选择候选值最少的空方格作为最理想的猜测方格。

创建空方格的规则解释了我们如何隐式地迭代值列表,如下所示。为所有没有对应求解值的方格创建空方格(如第 5-7 行所示)。

1. definitions 
2.	set rowNum to a number in { 1,2,3,4,5,6,7,8,9} ; 
3.	set colNum to a number in { 1,2,3,4,5,6,7,8,9} ; 
4. if 
5.	 there is no resolved cell value 
6.	    where the row of this resolved cell value is rowNum 
7.	    and  the col of this resolved cell value is colNum, 
8. then  
9.	 insert object : create an open cell for row rowNum and column colNum ;

同样,向空方格添加候选值的规则也非常简单,如下所示。该规则会匹配内存存储器中的所有候选值,并将它们添加到对应的空方格中。

1. definitions 
2.	set openCell to a sudoku open cell ; 
3.	set candidate to a candidate cell value 
4.	  where the row of this candidate cell value is the row of openCell 
5.	  and the col of this candidate cell value is the col of openCell ; 
6. then
7.	  add the value of candidate  to the candidates of openCell ;

下面显示了应用最少候选值的规则。规则条件(第 6-7 行)将确保其他空方格的候选值不会比所选方格少,然后将所选方格作为进行猜测的最理想方格。

1. definitions 
2.	set openCell to a sudoku open cell
3.	  where the number of candidates of this sudoku open cell is at least 1 ; 
4. if 
5.	the most promising cell of 'the sudoku' is null and 
6.	there is no sudoku open cell where
7.	   the number of candidates of this sudoku open cell is less than the number of 
candidates of openCell , 	 		 
8. then
9.	set the most promising cell of 'the sudoku' to openCell ;

如果发现一个没有候选值的空方格,那么数独则无解。这通过下面所示的规则实现。

1. definitions 
2.	set openCell to a sudoku open cell ; 
3. if 
4.	it is not true that openCell has candidates  
5. then
6.	 make it true that 'the sudoku' has no solution ;

验证规则

最后一条规则是验证规则,这类规则将检查数独网格是否存在不一致的地方。这些验证规则将查看同一行或列或方格块中的两个方格是否分配了相同的值,这种行为是错误的。下面的清单展示了一个示例。该规则设置了一个指示符,指示当某个特定值存在于同一方格块中的两个已求解的方格内(第 2-5 行)时,数独无解(第 8 行)。

1. definitions 
2.	set 'resolvedCell1' to a resolved cell value ; 
3.	set 'resolvedCell2' to a resolved cell value  
4.	   where the block of this resolved cell value  equals the block of resolvedCell1 
5.	   and the value of this resolved cell value equals the value of resolvedCell1 ; 
6. then
7.	print "Block conflict in block " + the col of resolvedCell1 + " for value " + the 
value of resolvedCell2 ;
8.	make it true that 'the sudoku' has no solution ;

目前为止,本文介绍了解决数独使用的所有规则。要获得完整的规则列表,请从 下载 部分下载规则报告。

规则处理和优化

主要的规则流(安排规则的执行)如 图 3 所示。规则将通过下面的步骤执行:

  1. 初始化,在这一阶段将创建候选值和已求解的方格,并插入到规则引擎的内存存储器中。
  2. 验证,将检查已求解的方格,确保不存在不一致的地方。
  3. 求解,应用规则来解决数独。
  4. 猜测,如果应用规则后未找到答案,则进行猜测。
图 3. 主要规则流
主要规则流

注意,初始化不同于其他规则任务,它是一项操作任务。操作任务从输入数独中创建已求解的方格和候选值,如下列清单所示。

List resolvedCells = sudoku.buildResolvedCellValues();
List possibleCells = sudoku.buildCandidateCellValues();

int i;
for (i=0; i<resolvedCells.size(); i++) {
	context.insert(resolvedCells.get(i));
}
for (i=0; i<possibleCells.size(); i++) {
	context.insert(possibleCells.get(i));
}
context.insert(sudoku);

将候选值和已求解的方格放入到内存存储器中后,用于解决数独的规则就可以开始发挥作用了。然而,规则处理存在一个问题。当存在许多候选值时,规则引擎中的 Rete 网络将会超载,处理时间会变长。如果去掉复杂规则,那么可以在不到一秒的时间内找到答案。这一点很容易理解,因为复杂规则模式生成的 Rete 网络行为的数量非常庞大。例如,如果内存存储器中有 500 个候选值,而一个隐性三元组对 10 个不同的候选值进行匹配,那么 Rete 网络需要执行大约 50010 次排列组合!

匹配复杂规则模式的成本将随着内存存储器中候选值的数量呈指数级增长。要减少这些复杂规则的处理开销,可以将它们划分到不同的规则任务中,专门处理简单规则并不能排除掉的候选值。因为简单规则可以显著减少候选值的数量,所以这将对性能产生积极的影响。图 4 描述了采用这种优化后的规则流,其中在完成简单的数独规则后,将在一个单独的规则任务中执行复杂规则。

图 4. 优化后的规则流
优化后的规则流

可以看到,这种优化的一个缺点是向规则流中添加了复杂性。不仅需要将规则划分为简单和复杂规则,并且复杂规则到简单规则之间有一个回送。这是因为复杂规则排除某个候选值后将触发简单规则执行其他所有推断。因此,如果触发任意复杂规则,则需要再次触发简单规则。然而,增加这种复杂性是值得的,因为这会显著提高性能。

要实现这种优化,需要设置一个变量作为复杂规则的操作,随后会将该变量用于规则流转换逻辑中。以下代码片段中的第 5 行显示了这一点,它摘取自复杂规则的操作部分。

1. If
2. …	
3. then 
4. …
5.	set 'redo flag' to true ;

深度优先搜索

前面小节中介绍的业务规则可以解决许多高难度的数独配置,但不能解决所有配置。有一些数独配置无法用这些业务规则来解决,在这种情况下,规则引擎必须使用试错法或猜测法。试错法通过结合使用规则引擎和深度优先启发实现。

深度优先搜索将从一个初始节点开始搜索一个树形结构,并尽可能从一个分支开始搜索,在到达目标节点或分支的尽头后停止或回溯。这是一种未知式搜索,在从列表或候选节点中选择节点时不会按照目标进行选择。另一方面,深度优先搜索使用了一些启发功能来判断哪个节点是最佳的搜索节点。

搜索树中的节点其实就是一种数独配置。通过猜测某个空格的值或执行规则引擎,深度优先搜索算法从搜索树中的一个节点前进到下一个节点。执行规则引擎会导致以下三种情形之一:1) 规则引擎找到一个解决方法;2) 规则引擎判断数独网格中出现不一致或不存在解决方法; 3) 规则引擎停止执行,网格未填充完整,因为没有规则可供使用。在第一种情况中,深度优先搜索求解器将停止运行;在第二种情况中,求解器将原路返回;在第三种情况中,除非已经接近最大深度并返回,否则求解器将通过猜测到达下一个节点。这里的深度是指沿着搜索路径所做的猜测的数量。

图 5 显示了这种混合式深度优先搜索求解器的一个示例。在这个示例中,求解器将用起始节点 S 调用规则引擎。规则引擎应用业务逻辑并填充一些方格值,这将导致对节点 N1 进行搜索。然而,应用业务规则并没有找到解决方法,因此求解器会尝试使用试错法。它将对空格 N1 进行猜测,从而引出节点 N2。该节点经过规则引擎处理后引出节点 N3。由于这并不是最终状态,因此将通过猜测 N3 获得下一个节点 N4。当对 N4 调用规则引擎后,它将返回报告表明该配置没有可行的解决方案;换而言之,这是一个无解的配置。现在,求解器将返回到 N3 并对该方格应用不同的猜测(步骤 6),从而导致节点 N6。从步骤 6-13 可以看到,所有节点都是死结。因此,求解器将一直返回到节点 N1 并对于所猜测的方格应用下一个可能的值(步骤 14)。当这个配置被传递给规则引擎后,它将找到解决方案 N15。

图 5. 结合使用深度优先搜索和规则
结合使用深度优先搜索和规则

盲目的深度优先搜索策略通过为第一个空格选择第一个可行的候选值并进行猜测。这意味着可能会选择包含 9 个候选值的空格,而不是选择只有两个候选值的空格。在选择包含 9 个可能值的空格时,从统计学上讲,前四个候选值会导致进入死胡同。当选中的方格只有两个候选值时,第一次猜对的概率是 0.5 —— 远高于第一种情况的 1/9 的概率。因此,选择最有希望猜对的方格将对深度优先搜索的性能会产生巨大影响。当没有规则可以应用、数独面板配置没有全部填完并且不存在不一致的地方的时候,我们将会使用规则引擎识别出最有可能猜对的方格。如前文所述,业务规则应用了一种简单的启发方法来判断最有可能猜对的方格 —— 选择候选值数量最少的方格。

这种混合式方法结合使用了业务规则和可以执行返回的算法搜索,不仅可以用于解决数独,还可以解决其他约束满足问题,比如产品配置。认识到这一点后,人们提出了一种通用的算法,可以使用任何规则引擎并结合使用任意搜索树。图 6 描述了用于表示关键元素的结构。搜索树上的每个节点都由一个 INode 接口表示,该接口使用一些方法判断节点是目标状态还是死胡同。它还使用一种方法判断在返回后下一个要进行猜测的子节点。在我们的示例中,下一个子节点就是应用猜测后得出的节点。规则引擎使用一个 run 方法,使用 INode 作为参数并返回应用了所有业务规则后的新节点。

图 6. 混合深度优先搜索模型
混合深度优先搜索模型

用于应用混合方法的算法获得一个递归形式,如下所示。

1. solve (node, parent, depth)
2. if (depth > MAX_DEPTH) return no_solution
3. node = run rules (node)
4. if (node is goal state), return node
5. else
6.	if (node is dead end) return no_solution
7.	else // partial solution found
8.	while (there is a get next best_child and solution_node is not found)
9.	    solution_node =  solve (best_child, node, depth + 1)
10.	return solution_node

The corresponding Java snippet for the HeuristicDfsSolver is:

1. public INode solve(INode node, INode parent, int currentDepth) {
2.	INode returnNode = null;
3.	if (currentDepth > maxDepth) return returnNode;
4.	//run rules
5.	node = ruleEngine.run(node);
6.	if (node.isGoalState()) {
7.	   returnNode = node;
8.	} else {
9.	   if (node.isDeadEnd()) { // dead end
10.			//do nothing -- returns a null
11.		} else { //partial solution found...explore children
12.			while (true) {
13.			      INode bestChild = node.getNextChild();
14.			      if (bestChild != null) {
15.				    returnNode = solve (bestChild, node, 
currentDepth + 1);
16.				} 
17.			      if (returnNode != null || bestChild == null) break; 
// continue until no more children or solution found
18.			}
19.		}
20.	}
21.	return returnNode;
22. }

汇总

在独立模式下解决数独的主要类是 SudokuSolver,如 图 7 中的类图所示。它使用 WebSphere Operational Decision Manager 实现规则引擎来执行数独业务规则。规则引擎的输入和输出为 SudokuWrapperNode,这是前面讨论的 INode 接口实现。

图 7. Sudoku Solver 类图
Sudoku Solver 类图

规则引擎使用嵌入式 WebSphere Operational Decision Management 规则引擎,即 IlrContext。此引擎的规则在规则引擎初始化期间从规则集 JAR 中检索并求解,如下面的片段所示。

1. private void init() throws FileNotFoundException, IOException {
2.	JarInputStream is = new JarInputStream(new FileInputStream(new File(
3.		rulesetPath)));
4.	
5.	IlrRulesetArchiveLoader rulesetloader = new IlrJarArchiveLoader(is);
6.	IlrRulesetArchiveParser rulesetparser = new IlrRulesetArchiveParser();
7.
8.	ruleset = new IlrRuleset();
9.	rulesetparser.setRuleset(ruleset);
10.
11.		boolean parsed = rulesetparser.parseArchive(rulesetloader);
12.		context = new IlrContext(ruleset);
13. 	}

SudokuNodeWrapper 使用由规则引擎确定的最有希望猜对的方格来判断下一个子节点,如 SudokuNodeWrapper 代码片段中第 15 行所示。下一个子节点是当前节点的复制,将对它应用对最有希望猜对的方格使用的最佳猜测。

1. @Override
2. public INode getNextChild() {
3.	Sudoku childSudoku = applyBestGuess();
4.	if (childSudoku == null) return null;
5.	SudokuWrapperNode child =  new SudokuWrapperNode(childSudoku);
6.	child.guess = guess;
7.	return child;
8. }
9. /**
10.  * Clone current node and apply best guess
11.  * @return
12.  */
13. private Sudoku applyBestGuess() {
14.	Sudoku child = null;
15.	SudokuOpenCell openCell = sudoku.getMostPromisingCell();
16.	if (openCell != null && openCell.hasCandidates()) {
17.		child = new Sudoku(sudoku);
18.		Collections.sort(openCell.getCandidates());
19.		int val = openCell.getCandidates().remove(0);			
20.		guess = child.createResolvedCellValue(openCell.getRow(), 
openCell.getCol(), 
val, SudokuValueAssignmentType.GUESSED);
21.	}
22.	return child;
23. }

SudokuSolver 获得数独网格的字符串表示,并应用本专栏讨论的混合式方法。在我的下一期专栏中,我们将使用一个部署到云中的 Web 应用程序来显示并解决数独,如 图 8 所示。

图 8. Web 上的数独求解器
Sudoku solver on the web

结束语

在本专栏中,您了解了如何在包含搜索和回溯功能的混合方法中使用规则。我特意为该混合方法提供了一个算法,您可以用该算法解决约束满足问题,并用它来解决数独。这种混合方法可以解决任何难度的有解的数独谜题。

我们还了解到,知识库中知识的数量与是否需要使用试错法呈反比关系。然而,规则复杂性与规则处理时间也呈指数级关系。因此,您需要进行权衡,确定什么时候停止增加过于复杂的规则,改为使用试错法。我们还了解了如何使用规则任务划分来减轻一些非常复杂的规则的处理负担。


下载

描述名字大小
数独业务规则报告Sudoku_Business_Rule_report.pdf244KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=928590
ArticleTitle=BPM 观点:使用云中的业务规则来解决数独,第 1 部分: 实现规则应用程序
publish-date=05022013