def _nakedSingleNumber( self ):
self._changed = False
for pos, validset in self._valid_set.items():
if len(validset)<=0:
self._invalid = False
elif len(validset) == 1:
num = validset.pop()
validset.add(num)
print 'pos', pos, 'has only one candidate: %d' %num
self._changed = True
return True
return False
def _hiddenSingleNumber( self ):
self._changed = False
for num, posset in self._candi_pos.items():
#_groupByRow,_groupByCol,_groupByBlock is helper function
rows = self._groupByRow(posset)
for r, row in rows.items():
if len(row)==1:
print 'row%d has only one position for num %d' %(r, num), row[0]
self._changed = True
return True
cols = self._groupByCol(posset)
for c, col in cols.items():
if len(col)==1:
print 'col%d has only one position for num %d' %(c, num), col[0]
self._changed = True
return True
blks = self._groupByBlock(posset)
for b, blk in blks.items():
if len(blk)==1:
print 'blk%d has only one position for num %d' %(b, num), blk[0]
self._changed = True
return True
return False
从我们对显式数对/三数集/四数集法的描述,可以看出,它们其实很相似,只不过涉及到的候选数的个数不相同罢了。显式数对法中,要求两个位置有且只有两个相同的候选数,显式三数集法中,要求有三个位置,他们可以有两个或三个候选数,但是他们涉及到的候选数合起来正好是三个;同理,显式四数集法中要求有四个位置,他们可以有两个、三个或四个候选数,但是他们涉及到的候选数合起来正好是四个。注意到的是这些个位置指的都是同一个房内的。因此我的思路是这样的:分别对行/列/宫进行分析,num表示数集的个数(num=2:数对,num=3:三数集,num=4:四数集),如果该位置的候选数个数小于num,说明这个位置是可选的,将它加入candidate list中。从candidate list中任选num个(当然前提是candidate list中至少有num个元素),求这些位置的候选数的并集,如果正好等于num,则说明他们形成了显式数集。这其中涉及到一个辅助函数,_get_n_from_m(n,m),即从m中任选n个元素而不导致重复,也就是我们的Cm取n,实现如下:
def _get_n_from_m( self, n, m ):
index = range(n)
while index[0]<=m-n:
yield index
i = n-1
if index[i]
index[i]+=1
else:
index[i-1]+=1
while i>0 and index[i-1]>=m-n+i:
i -= 1
index[i-1]+=1
for j in range(i,n):
index[j]=index[j-1]+1
这个函数很有用,在后面将多次使用到。有了这个函数,接下来实现我们的思路就不难了:
#nakedNumberSet occurs in a [house], [name] it 'row/column/block'
#num=2: pair
#num=3: tripple set
#num=4: quad set
def _helper_nakedNumberSet( self, house, name, num ):
for i, item in house.items():
candidate = []
#if a position has less than or equal to [num] candidates
#add it to the candidate position list
for pos in item:
if len(self._valid_set[pos])<=num:
candidate.append(pos)
#if candidate list has at least [num] elements
#get any [num] of positions
#and have union of candidate numbers of these position
#if this union has exactly [num] candidate numbers
#we can delete these candidate numbers from cells other than these position
if len(candidate)>=num:
for indices in self._get_n_from_m( num, len(candidate)):
uSet = set()
subset = []
toRemove = []
for index in indices:
uSet = uSet.union(self._valid_set[candidate[index]])
if len(uSet)==num:
subset = [ candidate[j] for j in indices ]
for j in subset:
item.remove(j)
for pos in item:
for n in uSet:
if n in self._valid_set[pos]:
toRemove.append( ( pos[0], pos[1], n ))
if toRemove:
print 'in %s%d,' %(name,i),
for (r,c) in subset:
print '(%d, %d)' %(r,c),
print 'have only candidates',
for n in uSet:
print '%d' %n,
self._removeCandidate(toRemove)
self._changed = True
return True
return False
同样的,隐式数对/三数集/四数集法也是相通的,只由一个num来区别。而且它的实现思路与显式数集法相仿,只不过由以位置由中心变成了以候选数为中心而已。首先是以房为单位,把那些只有少于num个候选位置的候选数给选出来,然后利用_get_n_from_m从中任选num个,看他们的并集是否正好是num个位置。
def _helper_hiddenNumberSet( self, housename, num ):
con = list()
for i in range(9):
con.append(dict())
groupBy = getattr( self, '_groupBy%s' %housename )
for n, posset in self._candi_pos.items():
house = groupBy( posset )
for i, items in house.items():
if len(items)<=num:
con[i][n]=set(items)
for i in range(9):
if len(con[i])>=num:
for indices in self._get_n_from_m( num, len(con[i])):
ks = con[i].keys()
nSet = []
uSet = set()
toRemove = []
for index in indices:
nSet.append(ks[index])
uSet = uSet.union(con[i][ks[index]])
if len(uSet)==num:
for pos in uSet:
for j in self._valid_set[pos]:
if j not in nSet:
toRemove.append( (pos[0], pos[1], j) )
if toRemove:
self._changed = True
print 'in %s%d,' %(housename, i),
for n in nSet:
print '%d,' %n,
print 'have only candidate postion',
for n in uSet:
print n,
self._removeCandidate(toRemove)
return True
return False
区块删减法基本可以用下面两点来概括:
1. 在某一区块即宫中,如果某个候选数只出现在一行(或者一列中),那么可以将该候选数从该行(或者该列)的其他单元格中删除,因为我们知道,每一个行/列/宫都有且只有一个候选数i,如果该候选数i在某个块中只出现在同一行,那么该行便已经确定在这个块中会有这个候选数了,所以该行的其他单元格就不能再有这个候选数了。
2. 在某一行/列中,如果某个候选数只出现在同个块中,那么可以将该候选数从该块中的其他单元格中删除。
def _regionDeletion( self ):
self._changed = False