Mastermind minimax算法

时间:2013-11-30 08:30:26

标签: python algorithm minimax

我正在尝试在python中实现Donald Knuth的算法,以便在不超过5个动作中进行密码破解。我已经多次检查了我的代码,它似乎遵循算法,如下所述: http://en.wikipedia.org/wiki/Mastermind_(board_game)#Five-guess_algorithm

然而,我知道一些秘密需要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 个答案:

答案 0 :(得分:12)

1。您的实施有什么问题

有四个错误。

  1. 此行的评论有误:

    m=max(HMList)#maximum [B,C], more left - less eliminated (min in minimax)
    

    这实际上是极小极大值中的“最大值”(从max的调用中应该清楚)。您试图找到最小化可能产生相同评估的秘密组的最大大小的猜测。在这里,我们找到了组的最大大小,因此这是“最大”。

  2. 这个错误导致你做出这个:

    if m>item1Max: #max of the guesses, the max in minimax
    

    在这里你需要采取最小值,而不是最大值

  3. 在以下几行中,您选择optionList中与item1生成相同评价的第一项:

    possibleGuess=[i[:] for i in optionList if   AdjustmentInverse(maxElement)==HowManyBc(item1[:],i[:])]
    nextGuess=possibleGuess[0][:]
    

    但那不对:我们想要的猜测是item1,而不是其他可以产生相同评价的猜测!

  4. 最后,您没有正确处理optionList只有一个剩余项目的情况。在这种情况下,所有可能的猜测同样善于区分这个项目,因此minimax程序不区分猜测。在这种情况下,您应该猜测optionList[0]

  5. 2。对您的代码的其他评论

    1. 变量名称选择不当。例如,item1是什么?这是您正在评估的猜测,所以它应该被称为possible_guess之类的东西?我怀疑你上面的错误§1.3部分是由于变量名称选择不当引起的。

    2. 有大量不必要的复制。您的所有[:]都毫无意义,可以删除。变量List1也毫无意义(为什么不只是分配给optionList?),nextGuess(不只是分配给guess?)

    3. 您构建的dummyList包含与最后一次猜测相匹配的所有可能的机密,但是您会丢弃dummyList中不属于optionList的所有条目。那么为什么不循环optionList并保持匹配的条目呢?像这样:

      optionList = [item for item in optionList if HowManyBc(guess, item)==BC]
      
    4. 您建立了一个表HMList,用于计算每种公牛和奶牛模式的出现次数。您已经注意到有14个可能的(牛,牛)对,因此您已经编写了函数AdjustmentAdjustmentInverse来在(公牛,牛)对和它们的指数之间来回映射在列表中。

      如果采用数据驱动方法并使用内置的list.index方法,这些函数可以实现更简单的实现:

      # 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之后,你不再需要AdjustmentInverse了。如果您将计数保留在collections.Counter而不是列表中,则可以避免使用Adjustment。所以而不是:

      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())
      
    5. 3。改进代码

      1. 使用标准库中的类collections.Counter,可以将猜测(您的函数HowManyBc)简化为三行。

        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中使用字母代码。阅读和输入的ACDB[0, 2, 3, 1]好得多。但是我的evaluate函数可以灵活地表示代码和猜测,只要您将它们表示为可比项目的序列,这样您就可以根据需要使用数字列表。

        另请注意,我已经编写了一些doctests:这些是在文档中同时提供示例并测试函数的快速方法。

      2. 函数itertools.product提供了一种构建代码列表的便捷方法,而无需编写四个嵌套循环:

        from itertools import product
        ALL_CODES = [''.join(c) for c in product('ABCDEF', repeat=4)]
        
      3. Knuth的五猜算法使用minimax principle。那么为什么不通过min的一系列调用max实现它呢?

        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)