表面上相等的集合列表在 Python 2.5 下表现不同的情况(我认为......)
四年前,我编写了一个数独解谜器,现在我试图了解它的工作原理,以便我可以将其部分内容重新用于 KenKen 解谜器。我认为我最好将循环压缩为列表推导式,并为变量选择更多不言自明的名称。
有一个 Puz 类,其中包含作为 (81) 个数字列表的输入谜题; 1 到 9 表示单元格的值已知,0 表示单元格的值未知。
Puz 类还包含拼图的工作版本,不同之处在于这里列表中的每一项 (81) 都是一个集合;如果单元格的答案已知,则该集合包含从 1 到 9 的一个值,例如 set([4]);如果答案未知,则该集合包含剩余的可能性,例如 set([3,5 ,7,9])。 当 Puz._init__(self, puz) 被调用时,工作列表中那些“可能”的集合被设置为 set([1,2,3,4,5,6,7,8,9]),并且第一个获得解决方案的步骤是删除该单元格的行、列和 3x3 块中显示为答案的所有值。
最初,工作列表是使用for循环填充的:对于0到80,如果是答案,则将答案作为集合放入,否则放入set(range(1, 10))。我不知道如何将这种条件式转化为列表理解,因此我将其分解为一个单独的“填充函数”,其中有 3 个版本如下所示。 fill_func 的不同之处在于它们的“非答案分支”:
return set( range( 1,(self.dim+1)))
return set( self.r_dim_1_based)
return self.set_dim_1_based
如您所见,越来越多的处理量被移到函数外部,回到初始化小变量的位置。
问题是,前两个变体进入数独解算器并按照原始代码的方式工作。但是——第三个变体中断了,说工作列表中的第六组是(或变成)空的。然而 --- 由所有三个变体产生的集合列表评估为相等:
p.W1 == p.W2 == p.W3 -->确实,
我很困惑。
下面是一些用于制作集合列表的代码:
#!/usr/bin/env python
import copy
from math import sqrt
'''
Puzzle #15, from The Guardian: 050624: #41: rated difficult
'''
puz = [
0,0,1, 9,0,0, 3,0,0,
0,0,0, 0,0,0, 2,0,0,
7,6,0, 0,2,0, 0,0,9,
3,0,0, 0,6,0, 0,0,5,
0,0,2, 1,0,3, 4,0,0,
4,0,0, 0,9,0, 0,0,3,
1,0,0, 0,3,0, 0,9,7,
0,0,4, 0,0,0, 0,0,0,
0,0,5, 0,0,8, 6,0,0
]
class GroupInfo: pass
class Puz(GroupInfo):
def __init__(self, puz):
self.A = copy.deepcopy(puz)
self.ncells = len( self.A)
self.r_ncells = range( 0,self.ncells)
self.dim = int( sqrt( self.ncells))
assert (self.dim ** 2) == self.ncells, "puz is not square"
self.r_dim_0_based = range( 0,self.dim)
self.r_dim_1_based = range( 1, self.dim + 1)
self.set_dim_1_based = set( self.r_dim_1_based) ## <<---- causes to fail!
##### with 'empty set at W[5]' !?!?!?
def W1_fill_func( val):
if (val == 0):
return set( range( 1,(self.dim+1)))
else:
return set( [val])
self.W1 = [ W1_fill_func( self.A[cid])
for cid in self.r_ncells ]
def W2_fill_func( val):
if (val == 0):
return set( self.r_dim_1_based)
else:
return set( [val])
self.W2 = [ W2_fill_func( self.A[cid])
for cid in self.r_ncells ]
def W3_fill_func( val):
if (val == 0):
return self.set_dim_1_based
else:
return set( [val])
self.W3 = [ W3_fill_func( self.A[cid])
for cid in self.r_ncells ]
return
#def Puz.__init__()
#class Puz
p = Puz(puz)
print p.W1 == p.W2 == p.W3
Four years ago I wrote a Sudoku puzzle solver, and now I'm trying to understand how it works so that I can reuse parts of it for a KenKen puzzle solver. I thought I'd better compactify loops into list comprehensions and pick more self-explanatory names for variables.
There's a class Puz which contains the input puzzle as a list of (81) digits; 1 through 9 where the cell's value is known, and 0 where it is not.
Class Puz also contains a working version of the puzzle, except that here each of the (81) items in the list is a set; where the answer for a cell is known, the set contains one value from 1 to 9, e.g., set([4]), and where the answer is unknown, the set contains the remaining possibilities, e.g., set([3,5,7,9]).
When Puz._init__(self, puz) is called, those "maybe" sets in the work list are set to set([1,2,3,4,5,6,7,8,9]), and the first step in getting to a solution is to strike out all the values which appear as answers in that cell's row, column, and 3x3 block.
Originally, the work list was filled in using a for loop: for 0 to 80, if it's an answer, put in the answer as a set, else put in set( range( 1, 10)). I couldn't figure out how to get this kind of conditional into a list comprehension, so I broke it out into a separate "filling function", of which 3 versions are shown below. The fill_funcs differ in their "not-an-answer branches":
return set( range( 1,(self.dim+1)))
return set( self.r_dim_1_based)
return self.set_dim_1_based
As you see, increasing amounts of processing are moved outside the function back to where the little variables are initialized.
The trouble is, the first two variations slip into the Sudoku solver and work exactly the way the original code did. BUT --- the third variation breaks, saying that the 6th set in the working list is (or becomes) empty. YET --- the lists of sets produced by all three variations evaluate as equal:
p.W1 == p.W2 == p.W3 --> True
I'm stumped.
Here's some code for making the lists of sets:
#!/usr/bin/env python
import copy
from math import sqrt
'''
Puzzle #15, from The Guardian: 050624: #41: rated difficult
'''
puz = [
0,0,1, 9,0,0, 3,0,0,
0,0,0, 0,0,0, 2,0,0,
7,6,0, 0,2,0, 0,0,9,
3,0,0, 0,6,0, 0,0,5,
0,0,2, 1,0,3, 4,0,0,
4,0,0, 0,9,0, 0,0,3,
1,0,0, 0,3,0, 0,9,7,
0,0,4, 0,0,0, 0,0,0,
0,0,5, 0,0,8, 6,0,0
]
class GroupInfo: pass
class Puz(GroupInfo):
def __init__(self, puz):
self.A = copy.deepcopy(puz)
self.ncells = len( self.A)
self.r_ncells = range( 0,self.ncells)
self.dim = int( sqrt( self.ncells))
assert (self.dim ** 2) == self.ncells, "puz is not square"
self.r_dim_0_based = range( 0,self.dim)
self.r_dim_1_based = range( 1, self.dim + 1)
self.set_dim_1_based = set( self.r_dim_1_based) ## <<---- causes to fail!
##### with 'empty set at W[5]' !?!?!?
def W1_fill_func( val):
if (val == 0):
return set( range( 1,(self.dim+1)))
else:
return set( [val])
self.W1 = [ W1_fill_func( self.A[cid])
for cid in self.r_ncells ]
def W2_fill_func( val):
if (val == 0):
return set( self.r_dim_1_based)
else:
return set( [val])
self.W2 = [ W2_fill_func( self.A[cid])
for cid in self.r_ncells ]
def W3_fill_func( val):
if (val == 0):
return self.set_dim_1_based
else:
return set( [val])
self.W3 = [ W3_fill_func( self.A[cid])
for cid in self.r_ncells ]
return
#def Puz.__init__()
#class Puz
p = Puz(puz)
print p.W1 == p.W2 == p.W3
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
正如您编码的那样, self.W3 包含对同一集合对象的许多引用 - 一旦您对其中一个引用调用任何变异方法,您就更改了所有其他引用。您需要确保
W3_fill_func
返回感兴趣的集合的独立副本,就像所有其他操作一样,例如将其返回更改为返回集合(self.set_dim_1_based)
。self.W3 as you've coded it contains many references to the same set object -- as soon as you call any mutating method on one of those references, you've changed all the others. You need to ensure
W3_fill_func
returns independent copies of the set of interest, just like all others do, e.g. by changing its return toreturn set(self.set_dim_1_based)
.