You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Python的OR-Tools中实现数独变体的行唯一集合约束:解决sorted()和min()函数的兼容问题

在Python的OR-Tools中实现数独变体的行唯一集合约束:解决sorted()和min()函数的兼容问题

我完全理解你遇到的困境——OR-Tools的CP-SAT变量是决策变量,在求解器找到解之前它们的值是未知的,所以Python原生的sorted()、min()这类处理确定值的函数根本没法直接用在这些变量上,生成9!排列的方法不仅效率极低,还完全走错了方向。

其实我们根本不需要折腾什么排列签名,核心需求是让每行的数字集合唯一,而不是排列不同。我们可以用一个更聪明的方式:给每个行的集合生成一个唯一的“哈希标识”,这个标识只和集合里的元素有关,和顺序无关,然后约束所有行的标识必须不同。

解决方案思路

我们可以利用二进制位的唯一性:对于0-11中的每个数字x,如果行里包含x,就把二进制的第x位设为1,否则为0。这样每个集合对应的二进制数(也就是十进制的哈希值)是完全唯一的——不同的集合不可能有相同的二进制表示,反过来相同的集合哈希值一定相同。

举个例子:集合{1,2,...,9}对应的二进制是0b11111111100(第1到9位是1),而集合{0,1,...,8}对应的是0b0111111111(第0到8位是1),这两个哈希值完全不同,完美符合我们的需求。

具体实现步骤

下面是完整的代码实现,我会一步步解释:

1. 先确保每行的单元格都是唯一的(经典数独行约束,如果你已经加过可以跳过)

# 先添加每行的数字唯一约束(经典数独要求)
for i in range(self.Rows):
    self.Model.AddAllDifferent([self.Cells[i][j] for j in range(self.Cols)])

2. 创建布尔变量标记每行是否包含某个数字

我们需要为每个行i和数字x(0-11)创建一个布尔变量present[(i, x)],用来表示行i中是否存在数字x:

from ortools.sat.python import cp_model

# 存储行i是否包含数字x的布尔变量
present = {}
for i in range(self.Rows):
    for x in range(12):  # 数字范围是0-11
        present[(i, x)] = self.Model.NewBoolVar(f"present_row_{i}_num_{x}")

3. 关联布尔变量和单元格变量

我们需要约束:当present[(i, x)]为True时,行i中至少有一个单元格等于x;当它为False时,行i中所有单元格都不等于x:

for i in range(self.Rows):
    for x in range(12):
        # 若present为True,则行中至少有一个单元格等于x
        self.Model.AddBoolOr([self.Cells[i][j] == x for j in range(self.Cols)]).OnlyEnforceIf(present[(i, x)])
        # 若present为False,则行中所有单元格都不等于x
        self.Model.AddBoolAnd([self.Cells[i][j] != x for j in range(self.Cols)]).OnlyEnforceIf(present[(i, x)].Not())

4. 约束每行恰好包含9个不同数字

因为题目要求每行有9个唯一数字,所以每行对应的布尔变量中恰好有9个为True:

for i in range(self.Rows):
    # 统计行i中为True的present变量数量,必须等于9
    self.Model.Add(sum(present[(i, x)] for x in range(12)) == 9)

5. 计算每行的唯一哈希值

用2的幂次计算哈希值,每个数字x对应2^x,把所有存在的数字对应的幂次相加,得到唯一的哈希值:

row_hashes = []
for i in range(self.Rows):
    # 构建哈希表达式:sum( present[(i,x)] * 2^x for x in 0..11 )
    hash_expr = sum(present[(i, x)] * (2 ** x) for x in range(12))
    # 创建哈希变量,范围是0到2^12-1(因为0-11共12个数字,最大哈希是2^0+2^1+...+2^11=4095)
    hash_var = self.Model.NewIntVar(0, 4095, f"row_hash_{i}")
    self.Model.Add(hash_var == hash_expr)
    row_hashes.append(hash_var)

6. 约束所有行的哈希值唯一

最后,只需要让所有行的哈希值都不同,就能保证每行的数字集合唯一:

# 确保所有行的哈希值(即数字集合)唯一
self.Model.AddAllDifferent(row_hashes)
print("Distinct row set constraints added successfully.")

扩展到列和子网格

如果需要对列或子网格也施加同样的“集合唯一”约束,只需要把上面的逻辑中的“行”换成“列”或“子网格”即可:

  • 对于列:遍历每一列j,收集self.Cells[i][j]作为列的单元格,然后重复上述步骤
  • 对于子网格:先定义每个子网格包含哪些单元格,比如3x3的子网格,然后收集这些单元格,重复上述步骤

为什么这个方法高效?

  • 完全不需要生成任何排列,避免了9!的爆炸式计算量
  • 所有约束都是OR-Tools CP-SAT原生支持的布尔约束和线性约束,求解器处理起来非常快
  • 哈希值的计算完全无冲突,保证了集合和哈希值的一一对应

备注:内容来源于stack exchange,提问作者Arun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:44:28