The “guess the number” game for arbitrary rational numbers?

后端 未结 8 1045
既然无缘
既然无缘 2020-12-12 09:12

I once got the following as an interview question:

I\'m thinking of a positive integer n. Come up with an algorithm that can guess it in O(lg n) quer

相关标签:
8条回答
  • 2020-12-12 09:40

    Here is yet another way to do it. If there is sufficient interest, I will try to fill out the details tonight, but I can't right now because I have family responsibilities. Here is a stub of an implementation that should explain the algorithm:

    low = 0
    high = 1
    bound = 2
    answer = -1
    while 0 != answer:
        mid = best_continued_fraction((low + high)/2, bound)
        while mid == low or mid == high:
            bound += bound
            mid = best_continued_fraction((low + high)/2, bound)
        answer = ask(mid)
        if -1 == answer:
            low = mid
        elif 1 == answer:
            high = mid
        else:
            print_success_message(mid)
    

    And here is the explanation. What best_continued_fraction(x, bound) should do is find the last continued fraction approximation to x with the denominator at most bound. This algorithm will take polylog steps to complete and finds very good (though not always the best) approximations. So for each bound we'll get something close to a binary search through all possible fractions of that size. Occasionally we won't find a particular fraction until we increase the bound farther than we should, but we won't be far off.

    So there you have it. A logarithmic number of questions found with polylog work.

    Update: And full working code.

    #! /usr/bin/python
    
    from fractions import Fraction
    import readline
    import sys
    
    operations = [0]
    
    def calculate_continued_fraction(terms):
        i = len(terms) - 1
        result = Fraction(terms[i])
        while 0 < i:
            i -= 1
            operations[0] += 1
            result = terms[i] + 1/result
        return result
    
    def best_continued_fraction (x, bound):
        error = x - int(x)
        terms = [int(x)]
        last_estimate = estimate = Fraction(0)
        while 0 != error and estimate.numerator < bound:
            operations[0] += 1
            error = 1/error
            term = int(error)
            terms.append(term)
            error -= term
            last_estimate = estimate
            estimate = calculate_continued_fraction(terms)
        if estimate.numerator < bound:
            return estimate
        else:
            return last_estimate
    
    def ask (num):
        while True:
            print "Next guess: {0} ({1})".format(num, float(num))
            if 1 < len(sys.argv):
                wanted = Fraction(sys.argv[1])
                if wanted < num:
                    print "too high"
                    return 1
                elif num < wanted:
                    print "too low"
                    return -1
                else:
                    print "correct"
                    return 0
    
            answer = raw_input("Is this (h)igh, (l)ow, or (c)orrect? ")
            if answer == "h":
                return 1
            elif answer == "l":
                return -1
            elif answer == "c":
                return 0
            else:
                print "Not understood.  Please say one of (l, c, h)"
    
    ow = Fraction(0)
    high = Fraction(1)
    bound = 2
    answer = -1
    guesses = 0
    while 0 != answer:
        mid = best_continued_fraction((low + high)/2, bound)
        guesses += 1
        while mid == low or mid == high:
            bound += bound
            mid = best_continued_fraction((low + high)/2, bound)
        answer = ask(mid)
        if -1 == answer:
            low = mid
        elif 1 == answer:
            high = mid
        else:
            print "Thanks for playing!"
            print "I needed %d guesses and %d operations" % (guesses, operations[0])
    

    It appears slightly more efficient in guesses than the previous solution, and does a lot fewer operations. For 101/1024 it required 19 guesses and 251 operations. For .98765 it needed 27 guesses and 623 operations. For 0.0123456789 it required 66 guesses and 889 operations. And for giggles and grins, for 0.0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 (that's 10 copies of the previous one) it required 665 guesses and 23289 operations.

    0 讨论(0)
  • 2020-12-12 09:44

    Remember that any rational number in (0, 1) can be represented as a finite sum of distinct (positive or negative) unit fractions. For example, 2/3 = 1/2 + 1/6 and 2/5 = 1/2 - 1/10. You can use this to perform a straight-forward binary search.

    0 讨论(0)
  • 2020-12-12 09:46

    I've got it! What you need to do is to use a parallel search with bisection and continued fractions.

    Bisection will give you a limit toward a specific real number, as represented as a power of two, and continued fractions will take the real number and find the nearest rational number.

    How you run them in parallel is as follows.

    At each step, you have l and u being the lower and upper bounds of bisection. The idea is, you have a choice between halving the range of bisection, and adding an additional term as a continued fraction representation. When both l and u have the same next term as a continued fraction, then you take the next step in the continued fraction search, and make a query using the continued fraction. Otherwise, you halve the range using bisection.

    Since both methods increase the denominator by at least a constant factor (bisection goes by factors of 2, continued fractions go by at least a factor of phi = (1+sqrt(5))/2), this means your search should be O(log(q)). (There may be repeated continued fraction calculations, so it may end up as O(log(q)^2).)

    Our continued fraction search needs to round to the nearest integer, not use floor (this is clearer below).

    The above is kind of handwavy. Let's use a concrete example of r = 1/31:

    1. l = 0, u = 1, query = 1/2. 0 is not expressible as a continued fraction, so we use binary search until l != 0.

    2. l = 0, u = 1/2, query = 1/4.

    3. l = 0, u = 1/4, query = 1/8.

    4. l = 0, u = 1/8, query = 1/16.

    5. l = 0, u = 1/16, query = 1/32.

    6. l = 1/32, u = 1/16. Now 1/l = 32, 1/u = 16, these have different cfrac reps, so keep bisecting., query = 3/64.

    7. l = 1/32, u = 3/64, query = 5/128 = 1/25.6

    8. l = 1/32, u = 5/128, query = 9/256 = 1/28.4444....

    9. l = 1/32, u = 9/256, query = 17/512 = 1/30.1176... (round to 1/30)

    10. l = 1/32, u = 17/512, query = 33/1024 = 1/31.0303... (round to 1/31)

    11. l = 33/1024, u = 17/512, query = 67/2048 = 1/30.5672... (round to 1/31)

    12. l = 33/1024, u = 67/2048. At this point both l and u have the same continued fraction term 31, so now we use a continued fraction guess. query = 1/31.

    SUCCESS!

    For another example let's use 16/113 (= 355/113 - 3 where 355/113 is pretty close to pi).

    [to be continued, I have to go somewhere]


    On further reflection, continued fractions are the way to go, never mind bisection except to determine the next term. More when I get back.

    0 讨论(0)
  • 2020-12-12 09:53

    I think I found an O(log^2(p + q)) algorithm.

    To avoid confusion in the next paragraph, a "query" refers to when the guesser gives the challenger a guess, and the challenger responds "bigger" or "smaller". This allows me to reserve the word "guess" for something else, a guess for p + q that is not asked directly to the challenger.

    The idea is to first find p + q, using the algorithm you describe in your question: guess a value k, if k is too small, double it and try again. Then once you have an upper and lower bound, do a standard binary search. This takes O(log(p+q)T) queries, where T is an upper bound for the number of queries it takes to check a guess. Let's find T.

    We want to check all fractions r/s with r + s <= k, and double k until k is sufficiently large. Note that there are O(k^2) fractions you need to check for a given value of k. Build a balanced binary search tree containing all these values, then search it to determine if p/q is in the tree. It takes O(log k^2) = O(log k) queries to confirm that p/q is not in the tree.

    We will never guess a value of k greater than 2(p + q). Hence we can take T = O(log(p+q)).

    When we guess the correct value for k (i.e., k = p + q), we will submit the query p/q to the challenger in the course of checking our guess for k, and win the game.

    Total number of queries is then O(log^2(p + q)).

    0 讨论(0)
  • 2020-12-12 10:03

    Okay, here's my answer using continued fractions alone.

    First let's get some terminology here.

    Let X = p/q be the unknown fraction.

    Let Q(X,p/q) = sign(X - p/q) be the query function: if it is 0, we've guessed the number, and if it's +/- 1 that tells us the sign of our error.

    The conventional notation for continued fractions is A = [a0; a1, a2, a3, ... ak]

    = a0 + 1/(a1 + 1/(a2 + 1/(a3 + 1/( ... + 1/ak) ... )))


    We'll follow the following algorithm for 0 < p/q < 1.

    1. Initialize Y = 0 = [ 0 ], Z = 1 = [ 1 ], k = 0.

    2. Outer loop: The preconditions are that:

      • Y and Z are continued fractions of k+1 terms which are identical except in the last element, where they differ by 1, so that Y = [y0; y1, y2, y3, ... yk] and Z = [y0; y1, y2, y3, ... yk + 1]

      • (-1)k(Y-X) < 0 < (-1)k(Z-X), or in simpler terms, for k even, Y < X < Z and for k odd, Z < X < Y.

    3. Extend the degree of the continued fraction by 1 step without changing the values of the numbers. In general, if the last terms are yk and yk + 1, we change that to [... yk, yk+1=∞] and [... yk, zk+1=1]. Now increase k by 1.

    4. Inner loops: This is essentially the same as @templatetypedef's interview question about the integers. We do a two-phase binary search to get closer:

    5. Inner loop 1: yk = ∞, zk = a, and X is between Y and Z.

    6. Double Z's last term: Compute M = Z but with mk = 2*a = 2*zk.

    7. Query the unknown number: q = Q(X,M).

    8. If q = 0, we have our answer and go to step 17 .

    9. If q and Q(X,Y) have opposite signs, it means X is between Y and M, so set Z = M and go to step 5.

    10. Otherwise set Y = M and go to the next step:

    11. Inner loop 2. yk = b, zk = a, and X is between Y and Z.

    12. If a and b differ by 1, swap Y and Z, go to step 2.

    13. Perform a binary search: compute M where mk = floor((a+b)/2, and query q = Q(X,M).

    14. If q = 0, we're done and go to step 17.

    15. If q and Q(X,Y) have opposite signs, it means X is between Y and M, so set Z = M and go to step 11.

    16. Otherwise, q and Q(X,Z) have opposite signs, it means X is between Z and M, so set Y = M and go to step 11.

    17. Done: X = M.

    A concrete example for X = 16/113 = 0.14159292

    Y = 0 = [0], Z = 1 = [1], k = 0
    
    k = 1:
    Y = 0 = [0; &#8734;] < X, Z = 1 = [0; 1] > X, M = [0; 2] = 1/2 > X.
    Y = 0 = [0; &#8734;], Z = 1/2 = [0; 2], M = [0; 4] = 1/4 > X.
    Y = 0 = [0; &#8734;], Z = 1/4 = [0; 4], M = [0; 8] = 1/8 < X.
    Y = 1/8 = [0; 8], Z = 1/4 = [0; 4], M = [0; 6] = 1/6 > X.
    Y = 1/8 = [0; 8], Z = 1/6 = [0; 6], M = [0; 7] = 1/7 > X.
    Y = 1/8 = [0; 8], Z = 1/7 = [0; 7] 
      --> the two last terms differ by one, so swap and repeat outer loop.
    
    k = 2:
    Y = 1/7 = [0; 7, &#8734;] > X, Z = 1/8 = [0; 7, 1] < X,
        M = [0; 7, 2] = 2/15 < X
    Y = 1/7 = [0; 7, &#8734;], Z = 2/15 = [0; 7, 2],
        M = [0; 7, 4] = 4/29 < X
    Y = 1/7 = [0; 7, &#8734;], Z = 4/29 = [0; 7, 4], 
        M = [0; 7, 8] = 8/57 < X
    Y = 1/7 = [0; 7, &#8734;], Z = 8/57 = [0; 7, 8],
        M = [0; 7, 16] = 16/113 = X 
        --> done!
    

    At each step of computing M, the range of the interval reduces. It is probably fairly easy to prove (though I won't do this) that the interval reduces by a factor of at least 1/sqrt(5) at each step, which would show that this algorithm is O(log q) steps.

    Note that this can be combined with templatetypedef's original interview question and apply towards any rational number p/q, not just between 0 and 1, by first computing Q(X,0), then for either positive/negative integers, bounding between two consecutive integers, and then using the above algorithm for the fractional part.

    When I have a chance next, I will post a python program that implements this algorithm.

    edit: also, note that you don't have to compute the continued fraction each step (which would be O(k), there are partial approximants to continued fractions that can compute the next step from the previous step in O(1).)

    edit 2: Recursive definition of partial approximants:

    If Ak = [a0; a1, a2, a3, ... ak] = pk/qk, then pk = akpk-1 + pk-2, and qk = akqk-1 + qk-2. (Source: Niven & Zuckerman, 4th ed, Theorems 7.3-7.5. See also Wikipedia)

    Example: [0] = 0/1 = p0/q0, [0; 7] = 1/7 = p1/q1; so [0; 7, 16] = (16*1+0)/(16*7+1) = 16/113 = p2/q2.

    This means that if two continued fractions Y and Z have the same terms except the last one, and the continued fraction excluding the last term is pk-1/qk-1, then we can write Y = (ykpk-1 + pk-2) / (ykqk-1 + qk-2) and Z = (zkpk-1 + pk-2) / (zkqk-1 + qk-2). It should be possible to show from this that |Y-Z| decreases by at least a factor of 1/sqrt(5) at each smaller interval produced by this algorithm, but the algebra seems to be beyond me at the moment. :-(

    Here's my Python program:

    import math
    
    # Return a function that returns Q(p0/q0,p/q) 
    #   = sign(p0/q0-p/q) = sign(p0q-q0p)*sign(q0*q)
    # If p/q < p0/q0, then Q() = 1; if p/q < p0/q0, then Q() = -1; otherwise Q()=0.
    def makeQ(p0,q0):
      def Q(p,q):
        return cmp(q0*p,p0*q)*cmp(q0*q,0)
      return Q
    
    def strsign(s):
      return '<' if s<0 else '>' if s>0 else '=='
    
    def cfnext(p1,q1,p2,q2,a):
      return [a*p1+p2,a*q1+q2]
    
    def ratguess(Q, doprint, kmax):
    # p2/q2 = p[k-2]/q[k-2]
      p2 = 1
      q2 = 0
    # p1/q1 = p[k-1]/q[k-1]
      p1 = 0
      q1 = 1
      k = 0
      cf = [0]
      done = False
      while not done and (not kmax or k < kmax):
        if doprint:
          print 'p/q='+str(cf)+'='+str(p1)+'/'+str(q1)
    # extend continued fraction
        k = k + 1
        [py,qy] = [p1,q1]
        [pz,qz] = cfnext(p1,q1,p2,q2,1)
        ay = None
        az = 1
        sy = Q(py,qy)
        sz = Q(pz,qz)
        while not done:
          if doprint:
            out = str(py)+'/'+str(qy)+' '+strsign(sy)+' X '
            out += strsign(-sz)+' '+str(pz)+'/'+str(qz)
            out += ', interval='+str(abs(1.0*py/qy-1.0*pz/qz))
          if ay:
            if (ay - az == 1):
              [p0,q0,a0] = [pz,qz,az]
              break
            am = (ay+az)/2
          else:
            am = az * 2
          [pm,qm] = cfnext(p1,q1,p2,q2,am)
          sm = Q(pm,qm)
          if doprint:
            out = str(ay)+':'+str(am)+':'+str(az) + '   ' + out + ';  M='+str(pm)+'/'+str(qm)+' '+strsign(sm)+' X '
            print out
          if (sm == 0):
            [p0,q0,a0] = [pm,qm,am]
            done = True
            break
          elif (sm == sy):
            [py,qy,ay,sy] = [pm,qm,am,sm]
          else:
            [pz,qz,az,sz] = [pm,qm,am,sm]     
    
        [p2,q2] = [p1,q1]
        [p1,q1] = [p0,q0]    
        cf += [a0]
    
      print 'p/q='+str(cf)+'='+str(p1)+'/'+str(q1)
      return [p1,q1]
    

    and a sample output for ratguess(makeQ(33102,113017), True, 20):

    p/q=[0]=0/1
    None:2:1   0/1 < X < 1/1, interval=1.0;  M=1/2 > X 
    None:4:2   0/1 < X < 1/2, interval=0.5;  M=1/4 < X 
    4:3:2   1/4 < X < 1/2, interval=0.25;  M=1/3 > X 
    p/q=[0, 3]=1/3
    None:2:1   1/3 > X > 1/4, interval=0.0833333333333;  M=2/7 < X 
    None:4:2   1/3 > X > 2/7, interval=0.047619047619;  M=4/13 > X 
    4:3:2   4/13 > X > 2/7, interval=0.021978021978;  M=3/10 > X 
    p/q=[0, 3, 2]=2/7
    None:2:1   2/7 < X < 3/10, interval=0.0142857142857;  M=5/17 > X 
    None:4:2   2/7 < X < 5/17, interval=0.00840336134454;  M=9/31 < X 
    4:3:2   9/31 < X < 5/17, interval=0.00379506641366;  M=7/24 < X 
    p/q=[0, 3, 2, 2]=5/17
    None:2:1   5/17 > X > 7/24, interval=0.00245098039216;  M=12/41 < X 
    None:4:2   5/17 > X > 12/41, interval=0.00143472022956;  M=22/75 > X 
    4:3:2   22/75 > X > 12/41, interval=0.000650406504065;  M=17/58 > X 
    p/q=[0, 3, 2, 2, 2]=12/41
    None:2:1   12/41 < X < 17/58, interval=0.000420521446594;  M=29/99 > X 
    None:4:2   12/41 < X < 29/99, interval=0.000246366100025;  M=53/181 < X 
    4:3:2   53/181 < X < 29/99, interval=0.000111613371282;  M=41/140 < X 
    p/q=[0, 3, 2, 2, 2, 2]=29/99
    None:2:1   29/99 > X > 41/140, interval=7.21500721501e-05;  M=70/239 < X 
    None:4:2   29/99 > X > 70/239, interval=4.226364059e-05;  M=128/437 > X 
    4:3:2   128/437 > X > 70/239, interval=1.91492009996e-05;  M=99/338 > X 
    p/q=[0, 3, 2, 2, 2, 2, 2]=70/239
    None:2:1   70/239 < X < 99/338, interval=1.23789953207e-05;  M=169/577 > X 
    None:4:2   70/239 < X < 169/577, interval=7.2514738621e-06;  M=309/1055 < X 
    4:3:2   309/1055 < X < 169/577, interval=3.28550190148e-06;  M=239/816 < X 
    p/q=[0, 3, 2, 2, 2, 2, 2, 2]=169/577
    None:2:1   169/577 > X > 239/816, interval=2.12389981991e-06;  M=408/1393 < X 
    None:4:2   169/577 > X > 408/1393, interval=1.24415093544e-06;  M=746/2547 < X 
    None:8:4   169/577 > X > 746/2547, interval=6.80448470014e-07;  M=1422/4855 < X 
    None:16:8   169/577 > X > 1422/4855, interval=3.56972657711e-07;  M=2774/9471 > X 
    16:12:8   2774/9471 > X > 1422/4855, interval=1.73982239227e-07;  M=2098/7163 > X 
    12:10:8   2098/7163 > X > 1422/4855, interval=1.15020646951e-07;  M=1760/6009 > X 
    10:9:8   1760/6009 > X > 1422/4855, interval=6.85549088053e-08;  M=1591/5432 < X 
    p/q=[0, 3, 2, 2, 2, 2, 2, 2, 9]=1591/5432
    None:2:1   1591/5432 < X < 1760/6009, interval=3.06364213998e-08;  M=3351/11441 < X 
    p/q=[0, 3, 2, 2, 2, 2, 2, 2, 9, 1]=1760/6009
    None:2:1   1760/6009 > X > 3351/11441, interval=1.45456726663e-08;  M=5111/17450 < X 
    None:4:2   1760/6009 > X > 5111/17450, interval=9.53679318849e-09;  M=8631/29468 < X 
    None:8:4   1760/6009 > X > 8631/29468, interval=5.6473816179e-09;  M=15671/53504 < X 
    None:16:8   1760/6009 > X > 15671/53504, interval=3.11036635336e-09;  M=29751/101576 > X 
    16:12:8   29751/101576 > X > 15671/53504, interval=1.47201634215e-09;  M=22711/77540 > X 
    12:10:8   22711/77540 > X > 15671/53504, interval=9.64157420569e-10;  M=19191/65522 > X 
    10:9:8   19191/65522 > X > 15671/53504, interval=5.70501257346e-10;  M=17431/59513 > X 
    p/q=[0, 3, 2, 2, 2, 2, 2, 2, 9, 1, 8]=15671/53504
    None:2:1   15671/53504 < X < 17431/59513, interval=3.14052228667e-10;  M=33102/113017 == X
    

    Since Python handles biginteger math from the start, and this program uses only integer math (except for the interval calculations), it should work for arbitrary rationals.


    edit 3: Outline of proof that this is O(log q), not O(log^2 q):

    First note that until the rational number is found, the # of steps nk for each new continued fraction term is exactly 2b(a_k)-1 where b(a_k) is the # of bits needed to represent a_k = ceil(log2(a_k)): it's b(a_k) steps to widen the "net" of the binary search, and b(a_k)-1 steps to narrow it). See the example above, you'll note that the # of steps is always 1, 3, 7, 15, etc.

    Now we can use the recurrence relation qk = akqk-1 + qk-2 and induction to prove the desired result.

    Let's state it in this way: that the value of q after the Nk = sum(nk) steps required for reaching the kth term has a minimum: q >= A*2cN for some fixed constants A,c. (so to invert, we'd get that the # of steps N is <= (1/c) * log2 (q/A) = O(log q).)

    Base cases:

    • k=0: q = 1, N = 0, so q >= 2N
    • k=1: for N = 2b-1 steps, q = a1 >= 2b-1 = 2(N-1)/2 = 2N/2/sqrt(2).

    This implies A = 1, c = 1/2 could provide desired bounds. In reality, q may not double each term (counterexample: [0; 1, 1, 1, 1, 1] has a growth factor of phi = (1+sqrt(5))/2) so let's use c = 1/4.

    Induction:

    • for term k, qk = akqk-1 + qk-2. Again, for the nk = 2b-1 steps needed for this term, ak >= 2b-1 = 2(nk-1)/2.

      So akqk-1 >= 2(Nk-1)/2 * qk-1 >= 2(nk-1)/2 * A*2Nk-1/4 = A*2Nk/4/sqrt(2)*2nk/4.

    Argh -- the tough part here is that if ak = 1, q may not increase much for that one term, and we need to use qk-2 but that may be much smaller than qk-1.

    0 讨论(0)
  • 2020-12-12 10:03

    Let's take the rational numbers, in reduced form, and write them out in order first of denominator, then numerator.

    1/2, 1/3, 2/3, 1/4, 3/4, 1/5, 2/5, 3/5, 4/5, 1/6, 5/6, ...
    

    Our first guess is going to be 1/2. Then we'll go along the list until we have 3 in our range. Then we will take 2 guesses to search that list. Then we'll go along the list until we have 7 in our remaining range. Then we will take 3 guesses to search that list. And so on.

    In n steps we'll cover the first 2O(n) possibilities, which is in the order of magnitude of efficiency that you were looking for.

    Update: People didn't get the reasoning behind this. The reasoning is simple. We know how to walk a binary tree efficiently. There are O(n2) fractions with maximum denominator n. We could therefore search up to any particular denominator size in O(2*log(n)) = O(log(n)) steps. The problem is that we have an infinite number of possible rationals to search. So we can't just line them all up, order them, and start searching.

    Therefore my idea was to line up a few, search, line up more, search, and so on. Each time we line up more we line up about double what we did last time. So we need one more guess than we did last time. Therefore our first pass uses 1 guess to traverse 1 possible rational. Our second uses 2 guesses to traverse 3 possible rationals. Our third uses 3 guesses to traverse 7 possible rationals. And our k'th uses k guesses to traverse 2k-1 possible rationals. For any particular rational m/n, eventually it will wind up putting that rational on a fairly big list that it knows how to do a binary search on efficiently.

    If we did binary searches, then ignored everything we'd learned when we grab more rationals, then we'd put all of the rationals up to and including m/n in O(log(n)) passes. (That's because by that point we'll get to a pass with enough rationals to include every rational up to and including m/n.) But each pass takes more guesses, so that would be O(log(n)2) guesses.

    However we actually do a lot better than that. With our first guess, we eliminate half the rationals on our list as being too big or small. Our next two guesses don't quite cut the space into quarters, but they don't come too far from it. Our next 3 guesses again don't quite cut the space into eighths, but they don't come too far from it. And so on. When you put it together, I'm convinced that the result is that you find m/n in O(log(n)) steps. Though I don't actually have a proof.

    Try it out: Here is code to generate the guesses so that you can play and see how efficient it is.

    #! /usr/bin/python
    
    from fractions import Fraction
    import heapq
    import readline
    import sys
    
    def generate_next_guesses (low, high, limit):
        upcoming = [(low.denominator + high.denominator,
                     low.numerator + high.numerator,
                     low.denominator, low.numerator,
                     high.denominator, high.numerator)]
        guesses = []
        while len(guesses) < limit:
            (mid_d, mid_n, low_d, low_n, high_d, high_n) = upcoming[0]
            guesses.append(Fraction(mid_n, mid_d))
            heapq.heappushpop(upcoming, (low_d + mid_d, low_n + mid_n,
                                         low_d, low_n, mid_d, mid_n))
            heapq.heappush(upcoming, (mid_d + high_d, mid_n + high_n,
                                      mid_d, mid_n, high_d, high_n))
        guesses.sort()
        return guesses
    
    def ask (num):
        while True:
            print "Next guess: {0} ({1})".format(num, float(num))
            if 1 < len(sys.argv):
                wanted = Fraction(sys.argv[1])
                if wanted < num:
                    print "too high"
                    return 1
                elif num < wanted:
                    print "too low"
                    return -1
                else:
                    print "correct"
                    return 0
    
            answer = raw_input("Is this (h)igh, (l)ow, or (c)orrect? ")
            if answer == "h":
                return 1
            elif answer == "l":
                return -1
            elif answer == "c":
                return 0
            else:
                print "Not understood.  Please say one of (l, c, h)"
    
    guess_size_bound = 2
    low = Fraction(0)
    high = Fraction(1)
    guesses = [Fraction(1,2)]
    required_guesses = 0
    answer = -1
    while 0 != answer:
        if 0 == len(guesses):
            guess_size_bound *= 2
            guesses = generate_next_guesses(low, high, guess_size_bound - 1)
        #print (low, high, guesses)
        guess = guesses[len(guesses)/2]
        answer = ask(guess)
        required_guesses += 1
        if 0 == answer:
            print "Thanks for playing!"
            print "I needed %d guesses" % required_guesses
        elif 1 == answer:
            high = guess
            guesses[len(guesses)/2:] = []
        else:
            low = guess
            guesses[0:len(guesses)/2 + 1] = []
    

    As an example to try it out I tried 101/1024 (0.0986328125) and found that it took 20 guesses to find the answer. I tried 0.98765 and it took 45 guesses. I tried 0.0123456789 and it needed 66 guesses and about a second to generate them. (Note, if you call the program with a rational number as an argument, it will fill in all of the guesses for you. This is a very helpful convenience.)

    0 讨论(0)
提交回复
热议问题