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

前端 未结 30 2794
一个人的身影
一个人的身影 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:19

    Are homework problems allowed here?

    This function does crude "base 5" math to generate a number between 0 and 6.

    function rnd7() {
        do {
            r1 = rnd5() - 1;
            do {
                r2=rnd5() - 1;
            } while (r2 > 1);
            result = r2 * 5 + r1;
        } while (result > 6);
        return result + 1;
    }
    
    0 讨论(0)
  • 2020-11-22 08:19

    Here is a working Python implementation of Adam's answer.

    import random
    
    def rand5():
        return random.randint(1, 5)
    
    def rand7():
        while True:
            r = 5 * (rand5() - 1) + rand5()
            #r is now uniformly random between 1 and 25
            if (r <= 21):
                break
        #result is now uniformly random between 1 and 7
        return r % 7 + 1
    

    I like to throw algorithms I'm looking at into Python so I can play around with them, thought I'd post it here in the hopes that it is useful to someone out there, not that it took long to throw together.

    0 讨论(0)
  • 2020-11-22 08:20

    The following produces a uniform distribution on {1, 2, 3, 4, 5, 6, 7} using a random number generator producing a uniform distribution on {1, 2, 3, 4, 5}. The code is messy, but the logic is clear.

    public static int random_7(Random rg) {
        int returnValue = 0;
        while (returnValue == 0) {
            for (int i = 1; i <= 3; i++) {
                returnValue = (returnValue << 1) + SimulateFairCoin(rg);
            }
        }
        return returnValue;
    }
    
    private static int SimulateFairCoin(Random rg) {
        while (true) {
            int flipOne = random_5_mod_2(rg);
            int flipTwo = random_5_mod_2(rg);
    
            if (flipOne == 0 && flipTwo == 1) {
                return 0;
            }
            else if (flipOne == 1 && flipTwo == 0) {
                return 1;
            }
        }
    }
    
    private static int random_5_mod_2(Random rg) {
        return random_5(rg) % 2;
    }
    
    private static int random_5(Random rg) {
        return rg.Next(5) + 1;
    }    
    
    0 讨论(0)
  • 2020-11-22 08:21
    int randbit( void )
    {
        while( 1 )
        {
            int r = rand5();
            if( r <= 4 ) return(r & 1);
        }
    }
    
    int randint( int nbits )
    {
        int result = 0;
        while( nbits-- )
        {
            result = (result<<1) | randbit();
        }
        return( result );
    }
    
    int rand7( void )
    {
        while( 1 )
        {
            int r = randint( 3 ) + 1;
            if( r <= 7 ) return( r );
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:22

    Here's a solution that fits entirely within integers and is within about 4% of optimal (i.e. uses 1.26 random numbers in {0..4} for every one in {0..6}). The code's in Scala, but the math should be reasonably clear in any language: you take advantage of the fact that 7^9 + 7^8 is very close to 5^11. So you pick an 11 digit number in base 5, and then interpret it as a 9 digit number in base 7 if it's in range (giving 9 base 7 numbers), or as an 8 digit number if it's over the 9 digit number, etc.:

    abstract class RNG {
      def apply(): Int
    }
    
    class Random5 extends RNG {
      val rng = new scala.util.Random
      var count = 0
      def apply() = { count += 1 ; rng.nextInt(5) }
    }
    
    class FiveSevener(five: RNG) {
      val sevens = new Array[Int](9)
      var nsevens = 0
      val to9 = 40353607;
      val to8 = 5764801;
      val to7 = 823543;
      def loadSevens(value: Int, count: Int) {
        nsevens = 0;
        var remaining = value;
        while (nsevens < count) {
          sevens(nsevens) = remaining % 7
          remaining /= 7
          nsevens += 1
        }
      }
      def loadSevens {
        var fivepow11 = 0;
        var i=0
        while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
        if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
        fivepow11 -= to9
        if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
        fivepow11 -= to8
        if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
        else loadSevens
      }
      def apply() = {
        if (nsevens==0) loadSevens
        nsevens -= 1
        sevens(nsevens)
      }
    }
    

    If you paste a test into the interpreter (REPL actually), you get:

    scala> val five = new Random5
    five: Random5 = Random5@e9c592
    
    scala> val seven = new FiveSevener(five)
    seven: FiveSevener = FiveSevener@143c423
    
    scala> val counts = new Array[Int](7)
    counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)
    
    scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
    i: Int = 100000000
    
    scala> counts
    res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
    14289332, 14283684)
    
    scala> five.count
    res1: Int = 125902876
    

    The distribution is nice and flat (within about 10k of 1/7 of 10^8 in each bin, as expected from an approximately-Gaussian distribution).

    0 讨论(0)
  • 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<bool> output;
    
        public ProbabilityCondensor(int chanceOfTrueReciprocal,
          Action<bool> 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).

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