【问题标题】:Mastermind minimax algorithm策划极小极大算法
【发布时间】:2013-11-30 08:30:26
【问题描述】:

我正在尝试在 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

如果有人能提供帮助,我将不胜感激!

谢谢,迈克

【问题讨论】:

  • “我知道有些秘密需要 6 或 7 步才能完成” - 请发布示例
  • 对于 [5, 3, 3, 4] 计数器是 7 对于 [5, 4, 4, 5] 计数器是 8

标签: python algorithm minimax


【解决方案1】:

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 仅剩一项的情况。在这种情况下,所有可能的猜测都同样擅长区分该项目,因此极小极大过程不会区分猜测。在这种情况下,您只需猜测optionList[0]

2。您的代码中的其他 cmets

  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 个可能的 (bull, cow) 对,因此您编写了函数 AdjustmentAdjustmentInverse 来在 (bull, cow) 对及其在列表中的索引之间来回映射。

    如果您采用数据驱动的方法并使用内置的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())
    

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。那么为什么不通过对max 的一系列调用中的min 来实现它呢?

    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)
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-24
    • 2019-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-29
    相关资源
    最近更新 更多