Expand a random range from 1–5 to 1–7

前端 未结 30 3135
一个人的身影
一个人的身影 2020-11-22 07:29

Given a function which produces a random integer in the range 1 to 5, write a function which produces a random integer in the range 1 to 7.

  1. What is a simple so
30条回答
  •  挽巷
    挽巷 (楼主)
    2020-11-22 08:23

    This answer is more an experiment in obtaining the most entropy possible from the Rand5 function. t is therefore somewhat unclear and almost certainly a lot slower than other implementations.

    Assuming the uniform distribution from 0-4 and resulting uniform distribution from 0-6:

    public class SevenFromFive
    {
      public SevenFromFive()
      {
        // this outputs a uniform ditribution but for some reason including it 
        // screws up the output distribution
        // open question Why?
        this.fifth = new ProbabilityCondensor(5, b => {});
        this.eigth = new ProbabilityCondensor(8, AddEntropy);
      } 
    
      private static Random r = new Random();
      private static uint Rand5()
      {
        return (uint)r.Next(0,5);
      }
    
      private class ProbabilityCondensor
      {
        private readonly int samples;
        private int counter;
        private int store;
        private readonly Action output;
    
        public ProbabilityCondensor(int chanceOfTrueReciprocal,
          Action output)
        {
          this.output = output;
          this.samples = chanceOfTrueReciprocal - 1;  
        }
    
        public void Add(bool bit)
        {
          this.counter++;
          if (bit)
            this.store++;   
          if (counter == samples)
          {
            bool? e;
            if (store == 0)
              e = false;
            else if (store == 1)
              e = true;
            else
              e = null;// discard for now       
            counter = 0;
            store = 0;
            if (e.HasValue)
              output(e.Value);
          }
        }
      }
    
      ulong buffer = 0;
      const ulong Mask = 7UL;
      int bitsAvail = 0;
      private readonly ProbabilityCondensor fifth;
      private readonly ProbabilityCondensor eigth;
    
      private void AddEntropy(bool bit)
      {
        buffer <<= 1;
        if (bit)
          buffer |= 1;      
        bitsAvail++;
      }
    
      private void AddTwoBitsEntropy(uint u)
      {
        buffer <<= 2;
        buffer |= (u & 3UL);    
        bitsAvail += 2;
      }
    
      public uint Rand7()
      {
        uint selection;   
        do
        {
          while (bitsAvail < 3)
          {
            var x = Rand5();
            if (x < 4)
            {
              // put the two low order bits straight in
              AddTwoBitsEntropy(x);
              fifth.Add(false);
            }
            else
            { 
              fifth.Add(true);
            }
          }
          // read 3 bits
          selection = (uint)((buffer & Mask));
          bitsAvail -= 3;     
          buffer >>= 3;
          if (selection == 7)
            eigth.Add(true);
          else
            eigth.Add(false);
        }
        while (selection == 7);   
        return selection;
      }
    }
    

    The number of bits added to the buffer per call to Rand5 is currently 4/5 * 2 so 1.6. If the 1/5 probability value is included that increases by 0.05 so 1.65 but see the comment in the code where I have had to disable this.

    Bits consumed by call to Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
    This is 3 + 3/8 + 3/64 + 3/512 ... so approx 3.42

    By extracting information from the sevens I reclaim 1/8*1/7 bits per call so about 0.018

    This gives a net consumption 3.4 bits per call which means the ratio is 2.125 calls to Rand5 for every Rand7. The optimum should be 2.1.

    I would imagine this approach is significantly slower than many of the other ones here unless the cost of the call to Rand5 is extremely expensive (say calling out to some external source of entropy).

提交回复
热议问题