An interview question: About Probability

前端 未结 10 1460
清酒与你
清酒与你 2021-01-29 21:14

An interview question:

Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1. Write a function g(x) using f(x) that 1/2 times returns 0, 1/2 times returns

相关标签:
10条回答
  • 2021-01-29 21:31

    As already mentioned your definition is not that good regarding probability. Usually it means that not only probability is good but distribution also. Otherwise you can simply write g(x) which will return 1,0,1,0,1,0,1,0 - it will return them 50/50, but numbers won't be random.

    Another cheating approach might be:

    var invert = false;
    function g(x) {
        invert = !invert;
        if (invert) return 1-f(x);
        return f(x);
    }
    

    This solution will be better than all others since it calls f(x) only one time. But the results will not be very random.

    0 讨论(0)
  • 2021-01-29 21:31

    A refinement of the same approach used in btilly's answer, achieving an average ~1.85 calls to f() per g() result (further refinement documented below achieves ~1.75, tbilly's ~2.6, Jim Lewis's accepted answer ~5.33). Code appears lower in the answer.

    Basically, I generate random integers in the range 0 to 3 with even probability: the caller can then test bit 0 for the first 50/50 value, and bit 1 for a second. Reason: the f() probabilities of 1/4 and 3/4 map onto quarters much more cleanly than halves.


    Description of algorithm

    btilly explained the algorithm, but I'll do so in my own way too...

    The algorithm basically generates a random real number x between 0 and 1, then returns a result depending on which "result bucket" that number falls in:

    result bucket      result
             x < 0.25     0
     0.25 <= x < 0.5      1
     0.5  <= x < 0.75     2
     0.75 <= x            3
    

    But, generating a random real number given only f() is difficult. We have to start with the knowledge that our x value should be in the range 0..1 - which we'll call our initial "possible x" space. We then hone in on an actual value for x:

    • each time we call f():
      • if f() returns 0 (probability 1 in 4), we consider x to be in the lower quarter of the "possible x" space, and eliminate the upper three quarters from that space
      • if f() returns 1 (probability 3 in 4), we consider x to be in the upper three-quarters of the "possible x" space, and eliminate the lower quarter from that space
      • when the "possible x" space is completely contained by a single result bucket, that means we've narrowed x down to the point where we know which result value it should map to and have no need to get a more specific value for x.

    It may or may not help to consider this diagram :-):

        "result bucket" cut-offs 0,.25,.5,.75,1
    
        0=========0.25=========0.5==========0.75=========1 "possible x" 0..1
        |           |           .             .          | f() chooses x < vs >= 0.25
        |  result 0 |------0.4375-------------+----------| "possible x" .25..1
        |           | result 1| .             .          | f() chooses x < vs >= 0.4375
        |           |         | .  ~0.58      .          | "possible x" .4375..1
        |           |         | .    |        .          | f() chooses < vs >= ~.58
        |           |         ||.    |    |   .          | 4 distinct "possible x" ranges
    

    Code

    int g() // return 0, 1, 2, or 3                                                 
    {                                                                               
        if (f() == 0) return 0;                                                     
        if (f() == 0) return 1;                                                     
        double low = 0.25 + 0.25 * (1.0 - 0.25);                                    
        double high = 1.0;                                                          
    
        while (true)                                                                
        {                                                                           
            double cutoff = low + 0.25 * (high - low);                              
            if (f() == 0)                                                           
                high = cutoff;                                                      
            else                                                                    
                low = cutoff;                                                       
    
            if (high < 0.50) return 1;                                              
            if (low >= 0.75) return 3;                                              
            if (low >= 0.50 && high < 0.75) return 2;                               
        }                                                                           
    }
    

    If helpful, an intermediary to feed out 50/50 results one at a time:

    int h()
    {
        static int i;
        if (!i)
        {
            int x = g();
            i = x | 4;
            return x & 1;
        }
        else
        {
            int x = i & 2;
            i = 0;
            return x ? 1 : 0;
        }
    }
    

    NOTE: This can be further tweaked by having the algorithm switch from considering an f()==0 result to hone in on the lower quarter, to having it hone in on the upper quarter instead, based on which on average resolves to a result bucket more quickly. Superficially, this seemed useful on the third call to f() when an upper-quarter result would indicate an immediate result of 3, while a lower-quarter result still spans probability point 0.5 and hence results 1 and 2. When I tried it, the results were actually worse. A more complex tuning was needed to see actual benefits, and I ended up writing a brute-force comparison of lower vs upper cutoff for second through eleventh calls to g(). The best result I found was an average of ~1.75, resulting from the 1st, 2nd, 5th and 8th calls to g() seeking low (i.e. setting low = cutoff).

    0 讨论(0)
  • 2021-01-29 21:35

    Since each return of f() represents a 3/4 chance of TRUE, with some algebra we can just properly balance the odds. What we want is another function x() which returns a balancing probability of TRUE, so that

    function g() {    
        return f() && x();
    }
    

    returns true 50% of the time.

    So let's find the probability of x (p(x)), given p(f) and our desired total probability (1/2):

    p(f) * p(x) =  1/2
    3/4  * p(x) =  1/2
           p(x) = (1/2) / 3/4
           p(x) =  2/3
    

    So x() should return TRUE with a probability of 2/3, since 2/3 * 3/4 = 6/12 = 1/2;

    Thus the following should work for g():

    function g() {
        return f() && (rand() < 2/3);
    }
    
    0 讨论(0)
  • 2021-01-29 21:39
    Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1
    

    Taking this statement literally, f(x) if called four times will always return zero once and 1 3 times. This is different than saying f(x) is a probabalistic function and the 0 to 1 ratio will approach 1 to 3 (1/4 vs 3/4) over many iterations. If the first interpretation is valid, than the only valid function for f(x) that will meet the criteria regardless of where in the sequence you start from is the sequence 0111 repeating. (or 1011 or 1101 or 1110 which are the same sequence from a different starting point). Given that constraint,

      g()= (f() == f())
    

    should suffice.

    0 讨论(0)
  • 2021-01-29 21:40

    Here is a solution based on central limit theorem, originally due to a friend of mine:

    /*
    Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1. Write a function g(x) using f(x) that 1/2 times returns 0, 1/2 times returns 1.
    */
    #include <iostream>
    #include <cstdlib>
    #include <ctime>
    #include <cstdio>
    using namespace std;
    
    int f() {
      if (rand() % 4 == 0) return 0;
      return 1;
    }
    
    int main() {
      srand(time(0));
      int cc = 0;
      for (int k = 0; k < 1000; k++) { //number of different runs
        int c = 0;
        int limit = 10000; //the bigger the limit, the more we will approach %50 percent
        for (int i=0; i<limit; ++i) c+= f();
        cc += c < limit*0.75 ? 0 : 1; // c will be 0, with probability %50
      }
      printf("%d\n",cc); //cc is gonna be around 500
      return 0;
    }
    
    0 讨论(0)
  • 2021-01-29 21:41

    Your solution is correct, if somewhat inefficient and with more duplicated logic. Here is a Python implementation of the same algorithm in a cleaner form.

    def g ():
        while True:
            a = f()
            if a != f():
                return a
    

    If f() is expensive you'd want to get more sophisticated with using the match/mismatch information to try to return with fewer calls to it. Here is the most efficient possible solution.

    def g ():
        lower = 0.0
        upper = 1.0
        while True:
            if 0.5 < lower:
                return 1
            elif upper < 0.5:
                return 0
            else:
                middle = 0.25 * lower + 0.75 * upper
                if 0 == f():
                    lower = middle
                else:
                    upper = middle
    

    This takes about 2.6 calls to g() on average.

    The way that it works is this. We're trying to pick a random number from 0 to 1, but we happen to stop as soon as we know whether the number is 0 or 1. We start knowing that the number is in the interval (0, 1). 3/4 of the numbers are in the bottom 3/4 of the interval, and 1/4 are in the top 1/4 of the interval. We decide which based on a call to f(x). This means that we are now in a smaller interval.

    If we wash, rinse, and repeat enough times we can determine our finite number as precisely as possible, and will have an absolutely equal probability of winding up in any region of the original interval. In particular we have an even probability of winding up bigger than or less than 0.5.

    If you wanted you could repeat the idea to generate an endless stream of bits one by one. This is, in fact, provably the most efficient way of generating such a stream, and is the source of the idea of entropy in information theory.

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