我正在尝试在python中实现唐纳德·克努斯(Donald Knuth)的算法,以不超过5步的代码破解策划者。我已经检查了我的代码几次,它似乎遵循算法,如下所示:
http://en.wikipedia.org/wiki/Mastermind_(board_game)#Five-guess_algorithm
但是,我发现某些 secret 需要7甚至8步才能完成。这是代码:
#returns how many bulls and cows there are
def HowManyBc(guess,secret):
invalid=max(guess)+1
bulls=0
cows=0
r=0
while r<4:
if guess[r]==secret[r]:
bulls=bulls+1
secret[r]=invalid
guess[r]=invalid
r=r+1
r=0
while r<4:
p=0
while p<4:
if guess[r]==secret[p] and guess[r]!=invalid:
cows=cows+1
secret[p]=invalid
break
p=p+1
r=r+1
return [bulls,cows]
# sends every BC to its index in HMList
def Adjustment(BC1):
if BC1==[0,0]:
return 0
elif BC1==[0,1]:
return 1
elif BC1==[0,2]:
return 2
elif BC1==[0,3]:
return 3
elif BC1==[0,4]:
return 4
elif BC1==[1,0]:
return 5
elif BC1==[1,1]:
return 6
elif BC1==[1,2]:
return 7
elif BC1==[1,3]:
return 8
elif BC1==[2,0]:
return 9
elif BC1==[2,1]:
return 10
elif BC1==[2,2]:
return 11
elif BC1==[3,0]:
return 12
elif BC1==[4,0]:
return 13
# sends every index in HMList to its BC
def AdjustmentInverse(place):
if place==0:
return [0,0]
elif place==1:
return [0,1]
elif place==2:
return [0,2]
elif place==3:
return [0,3]
elif place==4:
return [0,4]
elif place==5:
return [1,0]
elif place==6:
return [1,1]
elif place==7:
return [1,2]
elif place==8:
return [1,3]
elif place==9:
return [2,0]
elif place==10:
return [2,1]
elif place==11:
return [2,2]
elif place==12:
return [3,0]
elif place==13:
return [4,0]
# gives minimum of positive list without including its zeros
def MinimumNozeros(List1):
minimum=max(List1)+1
for item in List1:
if item!=0 and item<minimum:
minimum=item
return minimum
#list with all possible guesses
allList=[]
for i0 in range(0,6):
for i1 in range(0,6):
for i2 in range(0,6):
for i3 in range(0,6):
allList.append([i0,i1,i2,i3])
TempList=[[0,0,5,4]]
for secret in TempList:
guess=[0,0,1,1]
BC=HowManyBc(guess[:],secret[:])
counter=1
optionList=[]
for i0 in range(0,6):
for i1 in range(0,6):
for i2 in range(0,6):
for i3 in range(0,6):
optionList.append([i0,i1,i2,i3])
while BC!=[4,0]:
dummyList=[] #list with possible secrets for this guess
for i0 in range(0,6):
for i1 in range(0,6):
for i2 in range(0,6):
for i3 in range(0,6):
opSecret=[i0,i1,i2,i3]
if HowManyBc(guess[:],opSecret[:])==BC:
dummyList.append(opSecret)
List1=[item for item in optionList if item in dummyList]
optionList=List1[:] # intersection of optionList and dummyList
item1Max=0
for item1 in allList:
ListBC=[] # [list of all [bulls,cows] in optionList
for item2 in optionList:
ListBC.append(HowManyBc(item1[:],item2[:]))
HMList=[0]*14 # counts how many B/C there are for item2 in optionList
for BC1 in ListBC:
index=Adjustment(BC1)
HMList[index]=HMList[index]+1
m=max(HMList)#maximum [B,C], more left - less eliminated (min in minimax)
maxList=[i for i, j in enumerate(HMList) if j == m]
maxElement=maxList[0] #index with max
possibleGuess=[]
if m>item1Max: #max of the guesses, the max in minimax
item1Max=m
possibleGuess=[i[:] for i in optionList if AdjustmentInverse(maxElement)==HowManyBc(item1[:],i[:])]
nextGuess=possibleGuess[0][:]
guess=nextGuess[:]
BC=HowManyBc(guess[:],secret[:])
counter=counter+1
我得到:
对于[5、3、3、4]计数器为7
对于[5,4,4,5]计数器为8
如果有人可以帮忙,我将不胜感激!
谢谢,迈克
最佳答案
1.您的实现有什么问题
有四个错误。
m=max(HMList)#maximum [B,C], more left - less eliminated (min in minimax)
这实际上是minimax中的“最大值”(应该从对
max
的调用中清楚了)。您试图找到使产生相同评估的可能 secret 组的最大大小最小化的猜测。在这里,我们找到了组的最大大小,因此就是“最大”。 if m>item1Max: #max of the guesses, the max in minimax
在这里,您需要获取最小值,而不是最大值。
optionList
中的第一项,它将产生与item1
相同的评估:possibleGuess=[i[:] for i in optionList if AdjustmentInverse(maxElement)==HowManyBc(item1[:],i[:])]
nextGuess=possibleGuess[0][:]
但这是不对的:我们在这里想要的猜测是
item1
,而不是其他会产生相同评估结果的猜测! optionList
仅剩下一个项目的情况。在这种情况下,所有可能的猜测都同样擅长区分此项,因此minimax过程不会在猜测之间进行区分。在这种情况下,您应该只猜测optionList[0]
。 2.关于您的代码的其他注释
item1
是什么?这就是您正在评估的猜测,因此可以肯定地将其称为possible_guess
之类的东西?我怀疑您上面的§1.3错误部分是由于变量名选择不当造成的。 [:]
都是毫无意义的,可以删除。变量List1
也是毫无意义的(为什么不只分配给optionList
吗?),就像nextGuess
一样(不只是分配给guess
吗?)dummyList
,该dummyList
由与上一次猜测相匹配的所有可能的 secret 组成,但是随后您丢弃了optionList
中所有不在optionList
中的条目。那么,为什么不仅仅循环HMList
并保持匹配的条目呢?像这样:optionList = [item for item in optionList if HowManyBc(guess, item)==BC]
Adjustment
,该表将对每种模式的牛市和牛市的发生次数进行计数。您已经注意到一个事实,即有14个可能的(公牛,母牛)对,因此您编写了AdjustmentInverse
和list.index
函数来在(公牛,母牛)对及其列表中的索引之间来回映射。如果您采用数据驱动的方法并使用内置的
AdjustmentInverse
方法,则这些函数的实现可能简单得多:# Note that (3, 1) is not possible.
EVALUATIONS = [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1),
(1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (3, 0), (4, 0)]
def Adjustment(evaluation):
return EVALUATIONS.index(evaluation)
def AdjustmentInverse(index):
return EVALUATIONS[index]
但是在更正了上面的错误§1.3之后,您不再需要
Adjustment
了。如果将计数保留在 collections.Counter
而不是列表中,则可以避免HowManyBc
。因此,而不是:HMList=[0]*14 # counts how many B/C there are for item2 in optionList
for BC1 in ListBC:
index=Adjustment(BC1)
HMList[index]=HMList[index]+1
m=max(HMList)
您可以简单地写:
m = max(Counter(ListBC).values())
3.改进的代码
collections.Counter
类,可以将猜测(您的函数ACDB
)评估为仅三行。from collections import Counter
def evaluate(guess, secret):
"""Return the pair (bulls, cows) where `bulls` is a count of the
characters in `guess` that appear at the same position in `secret`
and `cows` is a count of the characters in `guess` that appear at
a different position in `secret`.
>>> evaluate('ABCD', 'ACAD')
(2, 1)
>>> evaluate('ABAB', 'AABB')
(2, 2)
>>> evaluate('ABCD', 'DCBA')
(0, 4)
"""
matches = sum((Counter(secret) & Counter(guess)).values())
bulls = sum(c == g for c, g in zip(secret, guess))
return bulls, matches - bulls
我碰巧更喜欢在Mastermind中使用字母作为代码。
[0, 2, 3, 1]
比evaluate
更易于阅读和键入。但是我的itertools.product
函数可以灵活地表示代码和猜测,只要您将它们表示为可比较项的序列即可,因此您可以根据需要使用数字列表。还要注意,我已经写了一些doctests:这是在文档中同时提供示例并测试功能的快速方法。
min
提供了一种便捷的方式来构建代码列表,而无需编写四个嵌套循环:from itertools import product
ALL_CODES = [''.join(c) for c in product('ABCDEF', repeat=4)]
max
的一系列调用中的 ojit_code 来实现它呢?def knuth(secret):
"""Run Knuth's 5-guess algorithm on the given secret."""
assert(secret in ALL_CODES)
codes = ALL_CODES
key = lambda g: max(Counter(evaluate(g, c) for c in codes).values())
guess = 'AABB'
while True:
feedback = evaluate(guess, secret)
print("Guess {}: feedback {}".format(guess, feedback))
if guess == secret:
break
codes = [c for c in codes if evaluate(guess, c) == feedback]
if len(codes) == 1:
guess = codes[0]
else:
guess = min(ALL_CODES, key=key)
这是一个示例运行:
>>> knuth('FEDA')
Guess AABB: feedback (0, 1)
Guess BCDD: feedback (1, 0)
Guess AEAC: feedback (1, 1)
Guess AFCC: feedback (0, 2)
Guess FEDA: feedback (4, 0)