ABC是一种在5×5棋盘上的逻辑拼图。每行和每列必须恰好包含3个字母(A、B、C)和2个空格。此外,每行和每列都提供关于该行/列中第一个字母的信息。这并不意味着第一个字母就在第一个单元格中,而是指该行中的第一个字母(第一个字母前可能有空格)。这里是带有信息的棋盘。
我需要帮助来弄清楚每行和每列中第一个字母的约束条件。
我已经有了每行和每列的约束条件,但就是无法找出如何确定行或列中第一个字母的确切位置。以下是我目前的进展。
*注意,我用1、2、3分别代表A、B、C,我用”_”(单空格)和”__”(双空格)代表空单元格,以便仍然符合’AllDifferentConstraint’的要求。
from constraint import *if __name__ == "__main__":problem = Problem(BacktrackingSolver())variables = []for i in range(0, 25): variables.append(i)domain= []for i in range(1,4): domain.append(i)domain.append("_")domain.append("__")problem.addVariables(variables, domain)for row in range(5): problem.addConstraint(AllDifferentConstraint(), [row * 5 + i for i in range(5)])for col in range(5): problem.addConstraint(AllDifferentConstraint(), [col * 5 + i for i in range(5)])solution = problem.getSolution()print(solution)
回答:
免责声明:
- 我的代码可能看起来有些过于复杂
- 对于最近一直在做约束编程的人来说,这个库提供的约束集看起来很奇怪和/或不完整(与工业级的Gecode相比)
- 我认为这个解决方案是一种权宜之计(但对于这个小问题来说已经足够了)
- 我保留了你的大部分代码,但将空格改成了数字(8, 9)以便于更好地可视化
基本思路假设维度是预先固定的,对于你所寻找的约束条件,存在确切的5种有效情况,从切片的前端来看:
0: _ __ TARGET ...dontcare1: __ _ TARGET ...dontcare2: _ TARGET ...dontcare3: __ TARGET ...dontcare4: TARGET ...dontcare
作为从SAT求解转向CP的人,我觉得用子句进行推理非常自然。上述内容可以很容易地用布尔逻辑表达(顺序与上述不同):
( pos0 = F )OR ( (pos0 = _ ) and (pos1 = F) )OR ( (pos0 = __) and (pos1 = F) )OR ( (pos0 = __) and (pos1 = _ ) and (pos2 = F)OR ( (pos0 = _ ) and (pos1 = __ ) and (pos2 = F)
通常人们会使用一个实现良好的约束/传播器(使用SAT技术),但这个库缺少常用的东西。
但幸运的是,有FunctionConstraint来救场,它允许我们构建一个简单的传播器。这实际上是一个非常好的设计(对于小型且相对简单的问題)!
一些说明:
clause
构建了我们的基于布尔逻辑的传播器(以非高效的方式)row
和clause
根据我们的需求返回基于1d的索引reverse
让我们在约束需要从末端而不是前端作用时重用逻辑- 我们使用部分函数应用来使调用更加紧凑
- numpy仅用作快速可视化工具(没有什么比从dict中推理矩阵解决方案更糟糕的了)
代码
""" ORIGINAL CODE """from constraint import *problem = Problem(BacktrackingSolver())variables = []for i in range(0, 25): variables.append(i)domain= []for i in range(1,4): domain.append(i)domain.append(8)domain.append(9)problem.addVariables(variables, domain)for row in range(5): problem.addConstraint(AllDifferentConstraint(), [row * 5 + i for i in range(5)])for col in range(5): problem.addConstraint(AllDifferentConstraint(), [col * 5 + i for i in range(5)])""" ADDITIONS """from functools import partial def clause(target, p0, p1, p2, p3, p4): wildcards = [8, 9] return (p0 == target) or \ ( (p0 in wildcards) and (p1 == target) ) or \ ( (p0 in wildcards) and (p1 in wildcards) and p2 == target ) row = lambda x: [x * 5 + i for i in range(5)]col = lambda x: [x + i * 5 for i in range(5)]reverse = lambda x: list(reversed(x))problem.addConstraint(partial(clause, 3), reverse(row(0))) # Cproblem.addConstraint(partial(clause, 2), reverse(row(1))) # Bproblem.addConstraint(partial(clause, 3), reverse(row(2))) # Cproblem.addConstraint(partial(clause, 1), row(2)) # Aproblem.addConstraint(partial(clause, 2), row(3)) # Bproblem.addConstraint(partial(clause, 1), col(0)) # Aproblem.addConstraint(partial(clause, 2), col(1)) # Bproblem.addConstraint(partial(clause, 1), col(3)) # Aproblem.addConstraint(partial(clause, 3), reverse(col(0))) # Cproblem.addConstraint(partial(clause, 3), reverse(col(2))) # Cproblem.addConstraint(partial(clause, 3), reverse(col(3))) # C""" SOLVE """solution = problem.getSolution()print(solution)""" VISUALIZE """import numpy as npmatrix = np.zeros((5, 5), dtype=int)matrix[np.unravel_index(range(5*5), (5,5))] = [solution[i] for i in range(5*5)]print(matrix)
输出
λ python C:\Tmp\constr.py{10: 9, 13: 3, 11: 1, 12: 2, 0: 9, 3: 1, 5: 1, 8: 2, 15: 9, 18: 8, 14: 8, 20: 3, 23: 9, 1: 2, 2: 8, 6: 9, 7: 3, 16: 2, 17: 3, 4: 3, 9: 8, 19: 1, 22: 8, 21: 2, 24: 1}[[9 2 8 1 3] [1 9 3 2 8] [9 1 2 3 8] [9 2 3 8 1] [3 2 8 9 1]]
结果出人意料地看起来是正确的 ;-), 与你最初的任务相比: