#coding=utf-8
#猜数字游戏8步以内的求解程序
#每次给出结果,要求输入xAyB,比如2A0B这样的结果,不分大小写
#
#解题思路很简单:
#1. 生成所有的四位不重复的0-9的数字组合的集合
#2. 随便找四个数字,比如0123
#3. 根据用户返回结果(xAyB),砍掉集合里面不符合结果的
#4. 根据现有数字组合,猜下一个,主要技术含量在这里:
# a. 贪心算法,每次都找当前步骤里最优的
# b. “最优”的定义:
# b1. 选择一个组合
# b2. 把这个组合和剩下的组合进行匹配,统计xAyB出现的次数,
# 比如0A0B出现了10次,1A3B出现了0次等等
# b3. 如果xAyB的所有可能出现的机会最为均等,那么这个选择的“区分度”就很大
# 这个可以通过信息量理论进行衡量,也可以简化为通过“最小标准差”来衡量
# b4. 遍历所有组合,找出“区分度”最大的
#5. 重复步骤3, 4,直到用户给出4A0B或者集合里面只剩下一个元素
#
#生成所有的四位不重复的0-9的数字组合
# by Leo Jay
# 灭掉了所有的判断语句,同时用展开的方法灭掉了set,目前已知最快的
# 应该不能更快了,因为这个版本已经没有多余的操作了
#其他方法参见:http://www.fayaa.com/code/view/114/
def init_set():
ret = []
for i in xrange(0, 10):
for j in xrange(i+1, 10):
for k in xrange(j+1, 10):
for l in xrange(k+1, 10):
ret.append((i, j, k, l))
ret.append((i, j, l, k))
ret.append((i, k, j, l))
ret.append((i, k, l, j))
ret.append((i, l, j, k))
ret.append((i, l, k, j))
ret.append((j, i, k, l))
ret.append((j, i, l, k))
ret.append((j, k, i, l))
ret.append((j, k, l, i))
ret.append((j, l, i, k))
ret.append((j, l, k, i))
ret.append((k, i, j, l))
ret.append((k, i, l, j))
ret.append((k, j, i, l))
ret.append((k, j, l, i))
ret.append((k, l, i, j))
ret.append((k, l, j, i))
ret.append((l, i, j, k))
ret.append((l, i, k, j))
ret.append((l, j, i, k))
ret.append((l, j, k, i))
ret.append((l, k, i, j))
ret.append((l, k, j, i))
return ret
#对给定的两组数,计算xAyB
#不知道能不能更快些
def get_match_ab(target, source):
la, lb = 0, 0
for (i, t) in enumerate(target):
for (j, s) in enumerate(source):
if s == t:
if i == j:
la += 1
else:
lb += 1
#break this loop since we already found match
break
return (la, lb)
#检查target与source是否符合aAbB
def match_guess(target, source, a, b):
(la, lb) = get_match_ab(target, source)
return la == a and lb == b
#nums: the number_set list to be checked
#guess: last guess
#a, b: the number of aAbB
#@return: the rest number_sets which matche last guess
def check_and_remove(nums, guess, a, b):
rest_nums = []
for num_set in nums:
if match_guess(num_set, guess, a, b):
rest_nums.append(num_set)
return rest_nums
#计算一个选择相对于选择集的“标准差”
def calc_standard_deviation(target, nums):
#a * 10 + b is used to indicate an "a & b" combination
ab_map = {}
#init ab_map
abs = (0, 1, 2, 3, 4, 10, 11, 12, 13, 20, 21, 22, 30, 31, 40)
for ab in abs:
ab_map[ab] = 0
#let's do the calculation
for num_set in nums:
(a, b) = get_match_ab(num_set, target)
ab_map[a * 10 + b] += 1
ab_counts = [ab_map[ab] for ab in abs]
total = sum(ab_counts)
avg = float(total) / len(abs)
sd = sum([(abc - avg)**2 for abc in ab_counts])
return sd
#根据现有集合寻找下一个集合
#采用“最小标准差”作为衡量标准
def next_guess(nums):
min_sd = 0
min_set = ()
touched = False
for num_set in nums:
sd = calc_standard_deviation(num_set, nums)
if not touched or min_sd > sd:
touched = True
min_set = num_set
min_sd = sd
return min_set
#猜数字的过程
def play():
nums = init_set()
guess = (0, 1, 2, 3)
done = False
retry = 1
while not done:
print retry, u'我猜是:', guess
retry += 1
a, b = 4, 4
while a + b > 4:
in_str = raw_input(u'请输入这次猜测的结果(xAyB):'.encode('GBK'))
try:
a = int(in_str[0])
b = int(in_str[2])
except:
print u'输入有误,请重新输入'
continue
if a + b > 4:
print u'输入有误,AB数目和大于4了,请重新输入'
nums = check_and_remove(nums, guess, a, b)
if len(nums) <= 0:
break
elif len(nums) == 1:
done = True
else:
print u' 范围缩小到%d个数字了...' % len(nums)
#为了效率所做的妥协:第一次返回结果以后直接取剩下的里面的第一个
#TODO:有时间就来做个全排列证明一下8次以内这种方法可行
#TODO:说不定八次都直接选取第一个就可以,那还要什么算法...:(
if retry < 3:
guess = nums[0]
else:
guess = next_guess(nums)
#拆数字结束了
if done: #出错则为False
print u'搞定! 目标数字是:', nums[0]
else:
print u'您输入的结果中某一步有错误,请检查一下'
print
#来玩玩
while True:
print
print '----------------------------'
print u'猜数字游戏八步以内求解程序'
print u'按 Ctrl + C 退出'
print '----------------------------'
print
play()