Project1

标题: 蛋疼数学程序帝!猜数字解法 [打印本页]

作者: DeathKing    时间: 2010-9-23 10:27
标题: 蛋疼数学程序帝!猜数字解法
本帖最后由 DeathKing 于 2010-9-24 12:57 编辑

  猜数字(又称 Bulls and Cows )是一种大概于20世纪中期兴起于英国的益智类小游戏。一般由两个人玩,也可以由一个人和电脑玩,在纸上、在网上都可以玩。这种游戏规则简单,但可以考验人的严谨和耐心。

  通常由两个人玩,一方出数字,一方猜。出数字的人要想好一个没有重复数字的4位数,不能让猜的人知道。猜的人就可以开始猜。每猜一个数字,出数者就要根据这个数字给出几A几B,其中A前面的数字表示位置正确的数的个数,而B前的数字表示数字正确而位置不对的数的个数。

  如正确答案为 5234,而猜的人猜 5346,则是 1A2B,其中有一个5的位置对了,记为1A,而3和4这两个数字对了,而位置没对,因此记为 2B,合起来就是 1A2B。

  接着猜的人再根据出题者的几A几B继续猜,直到猜中(即 4A0B)为止。

  假定正确答案为1234,则:
  0123 #=> 0A3B
  1423 #=> 1A3B
  1234 #=> 4A0B

  试找到一种行之有效的方法,使得程序在最短时间以及最小尝试次数内(不能超过10次)找到答案。

  可以是伪代码、Pascal语言、C/C++/C#语言、Basic语言、Ruby语言以及其他高级语言。并请各位自行编写生成正确答案与获得结果的函数。

[line]1[/line]
1、百度百科,猜数字:http://baike.baidu.com/view/358630.htm
2、Mastermind - Wiki:http://en.wikipedia.org/wiki/Mastermind_(board_game)
3、Mastermind 解法综述:http://mathworld.wolfram.com/Mastermind.html
4、Knuth (1976). The Computer As Master Mind:http://www.dcc.fc.up.pt/~sssousa/RM09101.pdf
5、Kooi (2005). Yet Another Mastermind Strategy: http://www.philos.rug.nl/~barteld/master.pdf
作者: 429259591    时间: 2010-9-23 10:29
这个我妈的手机有,可是我还是不明白游戏规则
作者: kenchenrong    时间: 2010-9-23 10:31
提示: 作者被禁止或删除 内容自动屏蔽
作者: david50407    时间: 2010-9-23 22:42
出数字的人要想好一个没有重复数字的4位数 <===关键

不论 题目走访 或 答案走访 "最多"都要重复 4 * 4 = 16 次运算
不过因为上面的关键
所以只要这样就好

假C/C++ language... :
#include <stdio.h>
#include <stdlib.h>
char ques[] = "1234"
char ans[] = "0123";
int ans_l[10] = {-1};
int a = 0;
int b = 0;
int temp = 0;
for (int i = 0; i < 4; ++i)
  ans_l[atoi(ans)] = i; // atoi() convert char to int, and fetch answer's char into array list
// 差点就end了 = =|
for (int i = 0; i < 4; ++i)
  if ((temp = ans_l[atoi(ques)]) >= 0) // if the question's char is in the answer
    if (temp == i) // A
      ++a;
    else // B
      ++b;
printf("%dA%dB", a, b);

共 4 + 4 次 = =b
作者: IamI    时间: 2010-9-24 10:05
本帖最后由 IamI 于 2010-9-24 13:09 编辑

第三轮?继续决绝占楼
……最高次数不过5040次,毫无压力= =b
  1. # 假定持有判定函数 answer(num1,num2,num3,num4),返回数组[A,B]
  2. # start
  3. for i in 0..9
  4.         for j in 0..9
  5.                 for k in 0..9
  6.                         for l in 0..9
  7.                                 next if (i = j or i = k or i = l or j = k or j = l or k = l)
  8.                                 return 1000 * i + 100 * j + 10 * k + l if answer(i,j,k,l)[0] == 4
  9.                         end
  10.                 end
  11.         end
  12. end
复制代码

作者: 神思    时间: 2010-9-24 12:05
楼上想复杂了。其实4个数可拆开,逐个判断。次数不超过4*10…
作者: orochi2k    时间: 2010-9-24 12:08
蛋痛邪恶程序帝表示偶觉得直接找到存放答案的内存地址然后读它比较快(被PIA飞)
作者: 小幽的马甲    时间: 2010-9-24 13:04
本帖最后由 小幽的马甲 于 2010-9-24 16:29 编辑
  1. class Guess
  2.   
  3.   def initialize(answer)
  4.     @ans = []
  5.     for i in 0..9999
  6.       @ans.push(i) if possible(i)
  7.     end
  8.     @s = answer
  9.   end
  10.   
  11.   def possible(i)
  12.     q = []
  13.     q[0..9] = 0
  14.     a = arr(i)
  15.     for n in a
  16.       return false if q[n] == 1
  17.       q[n] = 1
  18.     end
  19.   end

  20.   def arr(num)
  21.     a = []
  22.     for i in 0..3
  23.       a[3 - i] = num % 10
  24.       num /= 10
  25.     end
  26.     return a
  27.   end
  28.   
  29.   def check(a1, a2, a, b)
  30.     arr1 = arr(a1)
  31.     arr2 = arr(a2)
  32.     t = 0
  33.     for i in 0..3
  34.       t += 1 if arr1[i] == arr2[i]
  35.     end
  36.     return true if t != a
  37.     q = Array.new(10, 0)
  38.     t = 0
  39.     for i in arr1
  40.       q[i] = 1
  41.     end
  42.     for i in arr2
  43.       t += q[i]
  44.     end
  45.     return t != a + b
  46.   end

  47.   def ask(num,num2 = @s)
  48.     arr1 = arr(num2)
  49.     arr2 = arr(num)
  50.     a = 0
  51.     for i in 0..3
  52.       a += 1 if arr1[i] == arr2[i]
  53.     end
  54.     q = Array.new(10, 0)
  55.     b = -a
  56.     for i in arr1
  57.       q[i] = 1
  58.     end
  59.     for i in arr2
  60.       b += q[i]
  61.     end
  62.     return [a,b]
  63.   end

  64.   def getnum
  65.     ti = rand(@ans.size)
  66.     t = @ans.size
  67.     for o in 1..10
  68.       i1 = rand(@ans.size)
  69.       i = @ans[i1]
  70.       aa = 0
  71.       r1 = [1000/@ans.size].min
  72.       for r in 1..r1
  73.         j = @ans[rand(@ans.size)]
  74.         a, b = *ask(j,i)
  75.         s = @ans.size
  76.         for k in @ans
  77.           s -= 1 if check(j, k, a, b)
  78.         end
  79.         aa = s if s > aa
  80.       end
  81.       if aa <= t and aa > 0
  82.         t = aa
  83.         ti = i1
  84.       end
  85.     end
  86.     return ti
  87.   end
  88.   
  89.   def calc
  90.     time = 0
  91.     while @ans.size > 0
  92.       time += 1
  93.       i1 = ((time == 1) ? 123 : getnum)
  94.       i = @ans[i1]
  95.       a, b = *ask(i)
  96.       str = i.to_s
  97.       str = '0' + str if i < 1000
  98.       print "第#{time}次\n#{str}\n#{a}A#{b}B"
  99.       if a == 4
  100.         print "答案是#{str}"
  101.         return true
  102.       end
  103.       for num in [email protected]
  104.         @ans[num] = nil if check(i,@ans[num],a,b)
  105.       end
  106.       @ans[i1] = nil
  107.       @ans.compact!
  108.     end
  109.     p "Impossible!"
  110.     return false
  111.   end
  112. end

  113. guess = Guess.new(1234)
  114. guess.calc
复制代码
先丢半成品
关键在getnum这个方法上,先再想想= =
作者: david50407    时间: 2010-9-25 10:04
啊~原来是找出答案阿

建表?
  1. $question = "5968"

  2. $table = array.new
  3. for i in 0..9
  4.   for j in 0..9
  5.     for k in 0..9
  6.       for l in 0..9
  7.         $table.push("#{i}#{j}#{k}#{l}") # 建表
  8.       end
  9.     end
  10.   end
  11. end

  12. def check(c)
  13.   a = b = 0
  14.   if (p = $question.match("["+c+"]")) != nil # 测试是否有A或B的情况
  15.     for i in p
  16.       if $question.index(i) == c.index(i)
  17.         a += 1
  18.       else
  19.         b += 1
  20.       end
  21.     end
  22.   end
  23.   return a,b
  24. end

  25. def guess(g)
  26.   a,b = check(g) # 检查?A?B
  27.   $table.collect! {|t| check(t) == a,b ? t : nil}
  28.   $table = $table.compact
  29. end

  30. guess_times = 4
  31. guess("1234")
  32. guess("3456")
  33. guess("5678")
  34. guess("7890")
  35. for i in $table
  36.   guess_times+=1
  37.   if guess(i) == 4,0 # 4A0B
  38.     p "Ans: #{i}\nGuess #{guess_times} times"
  39.   end
  40. end
复制代码
应该就这样 (汗

作者: 紫苏    时间: 2010-9-26 04:24
本帖最后由 紫苏 于 2010-9-26 04:42 编辑

BullsAndCowsServices 模块:提供 Bulls and Cows 游戏和破译器的一些通用服务;
BullsAndCows 是 Bulls and Cows 游戏的类,其实例是一次游戏的实例;
BullsAndCowsBreaker 是用来解 Bulls and Cows 的破译器的类;

  1. module BullsAndCowsServices
  2.   # 生成无重复数位的随机四位数
  3.   def generate_digits
  4.     arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
  5.     arr.slice!(rand(9))*1000 +
  6.     arr.slice!(rand(9))*100 +
  7.     arr.slice!(rand(8))*10 +
  8.     arr.slice!(rand(7))
  9.   end
  10.   # 把四位整数转换为数位数组
  11.   def digits2arr(digits)
  12.     return [digits%10, digits/10%10, digits/100%10, digits/1000]
  13.   end
  14.   # 返回猜测的结果
  15.   def judge(ans, num)
  16.     a = b = 0
  17.     ans_digs = digits2arr(ans)
  18.     my_digs = digits2arr(num)
  19.     for i in 0..3
  20.       if my_digs[i] == ans_digs[i]
  21.         a += 1
  22.       elsif ans_digs.include?(my_digs[i])
  23.         b += 1
  24.       end
  25.     end
  26.     return a*10+b
  27.   end
  28. end

  29. class BullsAndCows
  30.   include BullsAndCowsServices
  31.   attr_reader :step
  32.   # 初始化
  33.   def initialize
  34.     @num = generate_digits
  35.     @step = 0
  36.   end
  37.   # 猜一个数字,返回结果
  38.   def guess(num)
  39.     result = judge(@num, num)
  40.     @step += 1
  41.     p "guessing #{num}, (#{result/10}A#{result%10}B), step ##@step"
  42.     return result
  43.   end
  44. end


  45. class BullsAndCowsBreaker
  46.   include BullsAndCowsServices
  47.   # 初始化
  48.   def initialize(bac)
  49.     @bac = bac
  50.     @possible_guesses = []
  51.     for i in 1023..9876
  52.       ans_digs = digits2arr(i)
  53.       if ans_digs.uniq.length == ans_digs.length
  54.         @possible_guesses << i
  55.       end
  56.     end
  57.   end
  58.   # 排除不可能的数字
  59.   def eliminate(guess, result)
  60.     @possible_guesses.delete_if do |possible_guess|
  61.       indirect_result = judge(guess, possible_guess)
  62.       indirect_result/10 != result/10 || indirect_result%10 != result%10
  63.     end
  64.   end
  65.   # 开始破译
  66.   def start_guessing(best_guess = 1023)
  67.     loop do
  68.       result = @bac.guess(best_guess)
  69.       eliminate(best_guess, result)
  70.       break if result == 40
  71.       best_guess = @possible_guesses[rand(@possible_guess)]
  72.     end
  73.   end
  74. end
复制代码
实际测试时可以:
  1.   bac = BullsAndCows.new
  2.   bacb = BullsAndCowsBreaker.new(bac)
  3.   bacb.start_guessing
复制代码
使用的步数在 2-8 之间,实际上理论上可以是一步到位,取决于第一次猜的时候使用的随机数字,当然概率极小;宏观方向测试:

  1. TIMES = 1000
  2. best = 999
  3. worst = 1
  4. sum = 0
  5. TIMES.times do |i|
  6.   bac = BullsAndCows.new
  7.   bacb = BullsAndCowsBreaker.new(bac)
  8.   bacb.start_guessing
  9.   sum += bac.step
  10.   worst = bac.step if bac.step > worst
  11.   best = bac.step if bac.step < best
  12. end
  13. p "best:#{best},worst:#{worst},average:#{sum.to_f/TIMES}"
复制代码
输出:
best:2,worst:8,average:5.478
平均需要猜 5.478 次 ……|||
这里用的方法很弱智,每次猜完根据结果排除不可能是答案的数字,然后在剩下的数字里随机选一个……|| 如果精通博弈论、决策论的强人应该有更好的方法在每次排除之后找最有可能是答案的数字……||
作者: 精灵使者    时间: 2010-9-26 08:27
本帖最后由 精灵使者 于 2010-9-26 09:05 编辑

我使用替换法来猜……我简要的写个算法吧。
首先,直接输入1234和5678(这前两个为固定式),然后判断他们的情况。
于是出现了两种情况。
如果里面的A,B里面加在一起等于4
那么可以肯定9,0被排除在数字之外。
如果A,B加在一起小于四的话就麻烦了。说明了,9和0之间会有一个数字是正确的。
于是,我们就尽量靠近两个极端
一个是0A0B(这四个数字都不是答案数字,可以排除很多情况)(可以更换其中的一些数字来观察情况,从而判断是不是答案数字)
一个是A+B = 4(这样的话全部数字都是答案数字,排除其余数字的情况)
然后再使用确认不是答案数字的字符来计算其他的数字是不是答案数字。
判断的差不多了的情况下,然后再计算以前的结果判断四个正确数字的位置。
基本上能够在8次以内猜中。
为了能减少猜的次数,有效的数字可以在各位出现,以判断有效的数字的正确位置。
所以,可以化整为零,每一个数字都做一个数组
分别为(数字,有效度,位置)
数字:0-9
有效度:(0-2)(0:未知 ,1:无效,2:有效)
位置(0-4)只有有效度有效的时候才会判断位置。
那么,最后的结果是
A 1 1
B 1 2
C 1 3
D 1 4
于是正确答案是ABCD。
每次答案的结果反馈进去,会自动生成下一个新的数字,来进行猜测。
整个思路就是:先猜测能完成的数字的大致范围(1234,5678)
然后再调换数字同时调整排序,根据结果来计算出正确的数字集(这个时侯可以用2-3次,而且可以用已鉴定完成的数字去鉴定未完成的数字)
找到数字的大致范围以后,计算它的位置(通过前几次的统计结果,确认正确的数字位置,其中如果有正确的数字被判断B的话,那么在此位上就不能是这个数字。提出的数字不能和以前的计算结果相抵触。)这样,随着提示越来越多,你就应该能算出正确的数字了。
作者: goahead    时间: 2010-9-28 17:53
提示: 作者被禁止或删除 内容自动屏蔽




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1