Python 如何解决这个问题;智囊团;猜谜游戏?
您将如何创建一个算法来解决以下难题“智囊团” 你的对手从六种颜色中选择了四种不同的颜色(黄色、蓝色、绿色、红色、橙色、紫色)。你必须猜出他们选择了哪一个,顺序是什么。每次猜测后,你的对手都会告诉你,你猜到的颜色中有多少(但不是哪一种)在正确的位置[“黑色”]是正确的颜色,有多少(但不是哪一种)在错误的位置[“白色”]。游戏结束时,你猜对了(4个黑人,0个白人) 例如,如果你的对手选择了(蓝色、绿色、橙色、红色),你猜(黄色、蓝色、绿色、红色),你将得到一个“黑色”(红色),两个白色(蓝色和绿色)。你猜得到的分数相同(蓝色、橙色、红色、紫色) 我感兴趣的是您将选择什么算法,以及(可选)如何将其转换为代码(最好是Python)。我对以下编码解决方案感兴趣:Python 如何解决这个问题;智囊团;猜谜游戏?,python,algorithm,Python,Algorithm,您将如何创建一个算法来解决以下难题“智囊团” 你的对手从六种颜色中选择了四种不同的颜色(黄色、蓝色、绿色、红色、橙色、紫色)。你必须猜出他们选择了哪一个,顺序是什么。每次猜测后,你的对手都会告诉你,你猜到的颜色中有多少(但不是哪一种)在正确的位置[“黑色”]是正确的颜色,有多少(但不是哪一种)在错误的位置[“白色”]。游戏结束时,你猜对了(4个黑人,0个白人) 例如,如果你的对手选择了(蓝色、绿色、橙色、红色),你猜(黄色、蓝色、绿色、红色),你将得到一个“黑色”(红色),两个白色(蓝色和绿色)
我已经发布了我自己的(详细的)Python解决方案,但这绝不是唯一或最好的方法,所以请发布更多!我不期待有一篇文章;) 关键工具:熵、贪婪性、分枝定界;Python、生成器、itertools、装饰非装饰图案 在回答这个问题时,我想建立一种有用函数的语言来探索这个问题。我将介绍这些函数,描述它们及其意图。最初,它们有大量的文档,使用doctest测试小型嵌入式单元测试;作为一种实现测试驱动开发的出色方式,我对这种方法论的评价再高不过了。但是,它不能很好地转换为StackOverflow,因此我将不以这种方式介绍它 首先,我需要几个标准模块和未来的导入(我使用Python 2.6) 我需要一个评分函数。最初,它返回一个元组(黑色、白色),但如果使用namedtuple,我发现输出会更清晰一些:
Pegs = collections.namedtuple('Pegs', 'black white')
def mastermindScore(g1,g2):
matching = len(set(g1) & set(g2))
blacks = sum(1 for v1, v2 in itertools.izip(g1,g2) if v1 == v2)
return Pegs(blacks, matching-blacks)
为了使我的解决方案更具概括性,我将特定于智囊团问题的任何内容作为关键字参数传入。因此,我创建了一个函数,创建这些参数一次,并使用**kwargs语法传递它。这还允许我在以后需要时轻松添加新属性。注意,我允许猜测包含重复,但限制对手选择不同的颜色;要改变这个,我只需要改变下面的G。(如果我想在对手的秘密中允许重复,我还需要更改得分函数。)
有时,我需要根据对集合中的每个元素应用函数的结果来划分集合。例如,数字1..10可以通过函数n%2分为偶数和奇数(赔率为1,偶数为0)。下面的函数返回这样一个分区,实现为从函数调用的结果到给出该结果的元素集(例如{0:evens,1:lobbs})的映射
我决定探索一种使用贪婪熵方法的求解器。在每一步中,它都会计算从每个可能的猜测中获得的信息,并选择信息量最大的猜测。随着可能性的增加,这将严重扩展(二次),但让我们尝试一下!首先,我需要一种方法来计算一组概率的熵(信息)。这只是-∑对数。但是,为了方便起见,我将允许未规范化的输入,即不加1:
def entropy(P):
total = sum(P)
return -sum(p*math.log(p, 2) for p in (v/total for v in P if v))
那么我将如何使用这个函数呢?对于一组给定的可能性V和一个给定的猜测g,我们从猜测中得到的信息只能来自评分函数:更具体地说,评分函数如何划分我们的可能性集。我们想做一个猜测,在剩下的可能性中,把它们分成最大数量的小集合,因为这意味着我们离答案更近了。这正是上面熵函数给出的一个数字:大量的小集合会比少量的大集合得分更高。我们所需要做的就是把它插进去
def decisionEntropy(V, g, score):
return entropy(collections.Counter(score(gi, g) for gi in V).values())
当然,在任何给定的步骤中,我们实际上会有一组剩余的可能性,V,和一组我们可能做出的猜测,G,我们需要选择熵最大化的猜测。此外,如果多个猜测具有相同的熵,则选择一个也可能是有效解的猜测;这保证了该方法将终止。我使用标准的python装饰取消装饰模式和内置的max方法来完成这项工作:
def bestDecision(V, G, score):
return max((decisionEntropy(V, g, score), g in V, g) for g in G)[2]
现在我需要做的就是反复调用这个函数,直到猜到正确的结果。我研究了这个算法的许多实现,直到找到一个似乎正确的。我的几个函数将以不同的方式处理此问题:一些函数列举所有可能的决策序列(对手可能做出的每个猜测一个),而另一些函数只对树中的一条路径感兴趣(如果对手已经选择了一个秘密,而我们只是试图找到解决方案)。我的解决方案是一个“懒惰树”,树的每一部分都是一个生成器,可以进行评估,也可以不进行评估,这样用户就可以避免他们不需要的昂贵计算。我还使用了另外两个命名的DTU
def entropy(P):
total = sum(P)
return -sum(p*math.log(p, 2) for p in (v/total for v in P if v))
def decisionEntropy(V, g, score):
return entropy(collections.Counter(score(gi, g) for gi in V).values())
def bestDecision(V, G, score):
return max((decisionEntropy(V, g, score), g in V, g) for g in G)[2]
Node = collections.namedtuple('Node', 'decision branches')
Branch = collections.namedtuple('Branch', 'result subtree')
def lazySolutionTree(G, V, score, endstates, **kwargs):
decision = bestDecision(V, G, score)
branches = (Branch(result, None if result in endstates else
lazySolutionTree(G, pV, score=score, endstates=endstates))
for (result, pV) in partition(V, score, decision).iteritems())
yield Node(decision, branches) # Lazy evaluation
def solver(scorer, **kwargs):
lazyTree = lazySolutionTree(**kwargs)
steps = []
while lazyTree is not None:
t = lazyTree.next() # Evaluate node
result = scorer(t.decision)
steps.append((t.decision, result))
subtrees = [b.subtree for b in t.branches if b.result == result]
if len(subtrees) == 0:
raise Exception("No solution possible for given scores")
lazyTree = subtrees[0]
assert(result in endstates)
return steps
def allSolutions(**kwargs):
def solutions(lazyTree):
return ((((t.decision, b.result),) + solution
for t in lazyTree for b in t.branches
for solution in solutions(b.subtree))
if lazyTree else ((),))
return solutions(lazySolutionTree(**kwargs))
def worstCaseSolution(**kwargs):
return max((len(s), s) for s in allSolutions(**kwargs)) [1]
def solutionLengthDistribution(**kwargs):
return collections.Counter(len(s) for s in allSolutions(**kwargs))
def solutionExists(maxsteps, G, V, score, **kwargs):
if len(V) == 1: return True
partitions = [partition(V, score, g).values() for g in G]
maxSize = max(len(P) for P in partitions) ** (maxsteps - 2)
partitions = (P for P in partitions if max(len(s) for s in P) <= maxSize)
return any(all(solutionExists(maxsteps-1,G,s,score) for l,s in
sorted((-len(s), s) for s in P)) for i,P in
sorted((-entropy(len(s) for s in P), P) for P in partitions))
def lowerBoundOnWorstCaseSolution(**kwargs):
for steps in itertools.count(1):
if solutionExists(maxsteps=steps, **kwargs):
return steps
Comparison = collections.namedtuple('Comparison', 'less greater equal')
def twoDScorer(x, y):
return Comparison(all(r[0] <= r[1] for r in zip(x, y)),
all(r[0] >= r[1] for r in zip(x, y)),
x == y)
def twoD():
G = set(itertools.product(xrange(5), repeat=2))
return dict(G = G, V = G, score = twoDScorer,
endstates = set(Comparison(True, True, True)))
def score(this, that):
'''Simple "Master Mind" scoring function'''
exact = len([x for x,y in zip(this, that) if x==y])
### Calculating "other" (white pegs) goes here:
### ...
###
return (exact,other)
other = 0
x = sorted(this) ## Implicitly converts to a list!
y = sorted(that)
while len(x) and len(y):
if x[0] == y[0]:
other += 1
x.pop(0)
y.pop(0)
elif x[0] < y[0]:
x.pop(0)
else:
y.pop(0)
other -= exact
other = 0
counters = dict()
for i in this:
counters[i] = counters.get(i,0) + 1
for i in that:
if counters.get(i,0) > 0:
other += 1
counters[i] -= 1
other -= exact
from itertools import product, tee
from random import choice
COLORS = 'red ', 'green', 'blue', 'yellow', 'purple', 'pink'#, 'grey', 'white', 'black', 'orange', 'brown', 'mauve', '-gap-'
HOLES = 4
def random_solution():
"""Generate a random solution."""
return tuple(choice(COLORS) for i in range(HOLES))
def all_solutions():
"""Generate all possible solutions."""
for solution in product(*tee(COLORS, HOLES)):
yield solution
def filter_matching_result(solution_space, guess, result):
"""Filter solutions for matches that produce a specific result for a guess."""
for solution in solution_space:
if score(guess, solution) == result:
yield solution
def score(actual, guess):
"""Calculate score of guess against actual."""
result = []
#Black pin for every color at right position
actual_list = list(actual)
guess_list = list(guess)
black_positions = [number for number, pair in enumerate(zip(actual_list, guess_list)) if pair[0] == pair[1]]
for number in reversed(black_positions):
del actual_list[number]
del guess_list[number]
result.append('black')
#White pin for every color at wrong position
for color in guess_list:
if color in actual_list:
#Remove the match so we can't score it again for duplicate colors
actual_list.remove(color)
result.append('white')
#Return a tuple, which is suitable as a dictionary key
return tuple(result)
def minimal_eliminated(solution_space, solution):
"""For solution calculate how many possibilities from S would be eliminated for each possible colored/white score.
The score of the guess is the least of such values."""
result_counter = {}
for option in solution_space:
result = score(solution, option)
if result not in result_counter.keys():
result_counter[result] = 1
else:
result_counter[result] += 1
return len(solution_space) - max(result_counter.values())
def best_move(solution_space):
"""Determine the best move in the solution space, being the one that restricts the number of hits the most."""
elim_for_solution = dict((minimal_eliminated(solution_space, solution), solution) for solution in solution_space)
max_elimintated = max(elim_for_solution.keys())
return elim_for_solution[max_elimintated]
def main(actual = None):
"""Solve a game of mastermind."""
#Generate random 'hidden' sequence if actual is None
if actual == None:
actual = random_solution()
#Start the game of by choosing n unique colors
current_guess = COLORS[:HOLES]
#Initialize solution space to all solutions
solution_space = all_solutions()
guesses = 1
while True:
#Calculate current score
current_score = score(actual, current_guess)
#print '\t'.join(current_guess), '\t->\t', '\t'.join(current_score)
if current_score == tuple(['black'] * HOLES):
print guesses, 'guesses for\t', '\t'.join(actual)
return guesses
#Restrict solution space to exactly those hits that have current_score against current_guess
solution_space = tuple(filter_matching_result(solution_space, current_guess, current_score))
#Pick the candidate that will limit the search space most
current_guess = best_move(solution_space)
guesses += 1
if __name__ == '__main__':
print max(main(sol) for sol in all_solutions())
import random
def main():
userAns = raw_input("Enter your tuple, and I will crack it in six moves or less: ")
play(ans=eval("("+userAns+")"),guess=(0,0,0,0),previousGuess=[])
def play(ans=(6,1,3,5),guess=(0,0,0,0),previousGuess=[]):
if(guess==(0,0,0,0)):
guess = genGuess(guess,ans)
else:
checker = -1
while(checker==-1):
guess,checker = genLogicalGuess(guess,previousGuess,ans)
print guess, ans
if not(guess==ans):
previousGuess.append(guess)
base = check(ans,guess)
play(ans=ans,guess=base,previousGuess=previousGuess)
else:
print "Found it!"
def genGuess(guess,ans):
guess = []
for i in range(0,len(ans),1):
guess.append(random.randint(1,6))
return tuple(guess)
def genLogicalGuess(guess,previousGuess,ans):
newGuess = list(guess)
count = 0
#Generate guess
for i in range(0,len(newGuess),1):
if(newGuess[i]==-1):
newGuess.insert(i,random.randint(1,6))
newGuess.pop(i+1)
for item in previousGuess:
for i in range(0,len(newGuess),1):
if((newGuess[i]==item[i]) and (newGuess[i]!=ans[i])):
newGuess.insert(i,-1)
newGuess.pop(i+1)
count+=1
if(count>0):
return guess,-1
else:
guess = tuple(newGuess)
return guess,0
def check(ans,guess):
base = []
for i in range(0,len(zip(ans,guess)),1):
if not(zip(ans,guess)[i][0] == zip(ans,guess)[i][1]):
base.append(-1)
else:
base.append(zip(ans,guess)[i][1])
return tuple(base)
main()
import random
from itertools import izip, imap
digits = 4
fmt = '%0' + str(digits) + 'd'
searchspace = tuple([tuple(map(int,fmt % i)) for i in range(0,10**digits)])
def compare(a, b, imap=imap, sum=sum, izip=izip, min=min):
count1 = [0] * 10
count2 = [0] * 10
strikes = 0
for dig1, dig2 in izip(a,b):
if dig1 == dig2:
strikes += 1
count1[dig1] += 1
count2[dig2] += 1
balls = sum(imap(min, count1, count2)) - strikes
return (strikes, balls)
def rungame(target, strategy, verbose=True, maxtries=15):
possibles = list(searchspace)
for i in xrange(maxtries):
g = strategy(i, possibles)
if verbose:
print "Out of %7d possibilities. I'll guess %r" % (len(possibles), g),
score = compare(g, target)
if verbose:
print ' ---> ', score
if score[0] == digits:
if verbose:
print "That's it. After %d tries, I won." % (i+1,)
break
possibles = [n for n in possibles if compare(g, n) == score]
return i+1
def strategy_allrand(i, possibles):
return random.choice(possibles)
if __name__ == '__main__':
hidden_code = random.choice(searchspace)
rungame(hidden_code, strategy_allrand)
Out of 10000 possibilities. I'll guess (6, 4, 0, 9) ---> (1, 0)
Out of 1372 possibilities. I'll guess (7, 4, 5, 8) ---> (1, 1)
Out of 204 possibilities. I'll guess (1, 4, 2, 7) ---> (2, 1)
Out of 11 possibilities. I'll guess (1, 4, 7, 1) ---> (3, 0)
Out of 2 possibilities. I'll guess (1, 4, 7, 4) ---> (4, 0)
That's it. After 5 tries, I won.
# SET UP
import random
import itertools
colors = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'ultra')
# ONE FUNCTION REQUIRED
def EvaluateCode(guess, secret_code):
key = []
for i in range(0, 4):
for j in range(0, 4):
if guess[i] == secret_code[j]:
key += ['black'] if i == j else ['white']
return key
# MAIN CODE
# choose secret code
secret_code = random.sample(colors, 4)
print ('(shh - secret code is: ', secret_code, ')\n', sep='')
# create the full list of permutations
full_code_list = list(itertools.permutations(colors, 4))
N_guess = 0
while True:
N_guess += 1
print ('Attempt #', N_guess, '\n-----------', sep='')
# make a random guess
guess = random.choice(full_code_list)
print ('guess:', guess)
# evaluate the guess and get the key
key = EvaluateCode(guess, secret_code)
print ('key:', key)
if key == ['black', 'black', 'black', 'black']:
break
# remove codes from the code list that don't match the key
full_code_list2 = []
for i in range(0, len(full_code_list)):
if EvaluateCode(guess, full_code_list[i]) == key:
full_code_list2 += [full_code_list[i]]
full_code_list = full_code_list2
print ('N remaining: ', len(full_code_list), '\n', full_code_list, '\n', sep='')
print ('\nMATCH after', N_guess, 'guesses\n')