3 行逻辑难题:lists/arrays 中序列约束的优化
3-in-a-row logic puzzle: optimisation for sequence constraints in lists/arrays
在下面的谜题中,我们尝试用以下方式用蓝色和白色方块填充网格:
- 不允许一行(或一列)3 个颜色相同。
- 每一行和每一列都有相等数量的蓝色和白色方块。
如果我们现在用 0 表示白色,用 1 表示蓝色,我们得到:
0 _ _ _ 1 _
_ 0 _ _ _ _
_ _ _ _ _ 0
1 _ _ 0 _ _
_ _ 1 1 _ _
_ 0 _ _ 1 _
我们可以很快验证
0 1 0 0 1 1
0 0 1 1 0 1
1 1 0 1 0 0
1 1 0 0 1 0
0 0 1 1 0 1
1 0 1 0 1 0
是这个例子的解决方案。
作为约束,我写了以下内容:
constraints(Board,N) :-
N2 is N // 2,
( for(I,1,N), param(Board,N2,N)
do
Row is Board[I,1..N],
Col is Board[1..N,I],
ic_global:sequence_total(N2,N2,1,2,3,Row),
ic_global:sequence_total(N2,N2,1,2,3,Col)
).
sequence_total/6 确保值 1 在 row/col 中恰好出现 N2 次(N 次的一半),并且指定的 3 个元素的 row/col 中的每个序列都应该包含值 1 的 1 到 2 倍(因此值 1 的 3 个方块不能彼此相邻出现)。
我得到以下 18x18 拼图实例 (*) 的结果:
Solved in 147 backtracks
Yes (10.39s cpu, solution 1)
看起来约束在执行任何搜索之前都很好地完成了它们的工作,因为需要 'only' 147 个回溯。然而 运行 时间对我来说似乎真的很长,尤其是与回溯的数量相比。我猜这是由于必须进行的所有序列检查?更改 search/6 中的任何 selection/choice 方法对 运行 时间几乎没有任何影响这一事实似乎证实了这一点。
如果是这样,是否有任何其他更有效的方法来限制 list/array 中的序列,使 N 个相同的元素彼此相邻并缩短 运行 时间?
编辑
使用下面@jschimpf提供的分解版本后,得到如下结果:
Solved in 310 backtracks
Yes (0.22s cpu, solution 1)
新的约束不如sequence/6强,我们确实需要多一点回溯,但是我们的运行时间从10.39秒降到了0.22秒,所以整体结果非常好可取。
示例数据:
本题使用的谜题(无回溯求解)
problem(p(6,1),[(1,1,0),(1,5,1),(2,2,0),(3,6,0),(4,1,1),(4,4,0),(5,3,1),(5,4,1),(6,2,0),(6,5,1)]).
我发布结果的谜题 (*):
problem(p(18,1),[(1,3,0),(1,9,0),(1,10,0),(1,12,0),(1,14,0),(1,18,1),(2,4,0),(2,7,1),(2,8,1),(3,2,1),(3,6,0),(3,16,0),(3,17,0),(4,2,1),(4,4,1),(4,10,1),(4,13,1),(4,18,1),(5,8,1),(5,10,1),(5,15,0),(5,16,1),(6,12,1),(7,3,0),(7,4,0),(7,6,1),(7,9,0),(7,12,1),(7,17,0),(8,1,1),(8,4,0),(8,8,1),(8,15,1),(8,16,1),(9,7,0),(9,10,0),(9,14,0),(10,2,1),(10,4,1),(10,6,1),(10,13,1),(11,7,0),(11,10,1),(12,1,1),(12,4,1),(12,7,1),(12,15,1),(12,16,1),(13,1,1),(13,6,0),(13,8,1),(13,10,0),(13,16,1),(14,5,1),(14,10,0),(14,13,1),(15,1,1),(15,3,1),(15,12,0),(15,13,1),(15,15,0),(16,2,1),(16,4,0),(16,12,0),(16,18,0),(17,9,0),(17,15,0),(17,18,0),(18,2,1),(18,8,1),(18,11,1),(18,15,1),(18,16,1)]).
事实证明,在这种情况下,sequence 约束的手工分解版本效率更高。使用例如
sequence_1_2([X1,X2,X3|Xs]) :- !,
S #:: 1..2,
X1+X2+X3 #= S,
sequence_1_2([X2,X3|Xs]).
sequence_1_2(_).
将任意3元素子序列的和约束为1或2,将sequence_total/6约束替换为
...,
sum(Row) #= N2,
sequence_1_2(Row),
这使求解时间缩短到 0.2 秒。
在下面的谜题中,我们尝试用以下方式用蓝色和白色方块填充网格:
- 不允许一行(或一列)3 个颜色相同。
- 每一行和每一列都有相等数量的蓝色和白色方块。
如果我们现在用 0 表示白色,用 1 表示蓝色,我们得到:
0 _ _ _ 1 _
_ 0 _ _ _ _
_ _ _ _ _ 0
1 _ _ 0 _ _
_ _ 1 1 _ _
_ 0 _ _ 1 _
我们可以很快验证
0 1 0 0 1 1
0 0 1 1 0 1
1 1 0 1 0 0
1 1 0 0 1 0
0 0 1 1 0 1
1 0 1 0 1 0
是这个例子的解决方案。
作为约束,我写了以下内容:
constraints(Board,N) :-
N2 is N // 2,
( for(I,1,N), param(Board,N2,N)
do
Row is Board[I,1..N],
Col is Board[1..N,I],
ic_global:sequence_total(N2,N2,1,2,3,Row),
ic_global:sequence_total(N2,N2,1,2,3,Col)
).
sequence_total/6 确保值 1 在 row/col 中恰好出现 N2 次(N 次的一半),并且指定的 3 个元素的 row/col 中的每个序列都应该包含值 1 的 1 到 2 倍(因此值 1 的 3 个方块不能彼此相邻出现)。
我得到以下 18x18 拼图实例 (*) 的结果:
Solved in 147 backtracks
Yes (10.39s cpu, solution 1)
看起来约束在执行任何搜索之前都很好地完成了它们的工作,因为需要 'only' 147 个回溯。然而 运行 时间对我来说似乎真的很长,尤其是与回溯的数量相比。我猜这是由于必须进行的所有序列检查?更改 search/6 中的任何 selection/choice 方法对 运行 时间几乎没有任何影响这一事实似乎证实了这一点。
如果是这样,是否有任何其他更有效的方法来限制 list/array 中的序列,使 N 个相同的元素彼此相邻并缩短 运行 时间?
编辑
使用下面@jschimpf提供的分解版本后,得到如下结果:
Solved in 310 backtracks
Yes (0.22s cpu, solution 1)
新的约束不如sequence/6强,我们确实需要多一点回溯,但是我们的运行时间从10.39秒降到了0.22秒,所以整体结果非常好可取。
示例数据:
本题使用的谜题(无回溯求解)
problem(p(6,1),[(1,1,0),(1,5,1),(2,2,0),(3,6,0),(4,1,1),(4,4,0),(5,3,1),(5,4,1),(6,2,0),(6,5,1)]).
我发布结果的谜题 (*):
problem(p(18,1),[(1,3,0),(1,9,0),(1,10,0),(1,12,0),(1,14,0),(1,18,1),(2,4,0),(2,7,1),(2,8,1),(3,2,1),(3,6,0),(3,16,0),(3,17,0),(4,2,1),(4,4,1),(4,10,1),(4,13,1),(4,18,1),(5,8,1),(5,10,1),(5,15,0),(5,16,1),(6,12,1),(7,3,0),(7,4,0),(7,6,1),(7,9,0),(7,12,1),(7,17,0),(8,1,1),(8,4,0),(8,8,1),(8,15,1),(8,16,1),(9,7,0),(9,10,0),(9,14,0),(10,2,1),(10,4,1),(10,6,1),(10,13,1),(11,7,0),(11,10,1),(12,1,1),(12,4,1),(12,7,1),(12,15,1),(12,16,1),(13,1,1),(13,6,0),(13,8,1),(13,10,0),(13,16,1),(14,5,1),(14,10,0),(14,13,1),(15,1,1),(15,3,1),(15,12,0),(15,13,1),(15,15,0),(16,2,1),(16,4,0),(16,12,0),(16,18,0),(17,9,0),(17,15,0),(17,18,0),(18,2,1),(18,8,1),(18,11,1),(18,15,1),(18,16,1)]).
事实证明,在这种情况下,sequence 约束的手工分解版本效率更高。使用例如
sequence_1_2([X1,X2,X3|Xs]) :- !,
S #:: 1..2,
X1+X2+X3 #= S,
sequence_1_2([X2,X3|Xs]).
sequence_1_2(_).
将任意3元素子序列的和约束为1或2,将sequence_total/6约束替换为
...,
sum(Row) #= N2,
sequence_1_2(Row),
这使求解时间缩短到 0.2 秒。