Seven-sided dice from five-sided dice

From Rosetta Code
Revision as of 06:06, 13 September 2009 by rosettacode>Avmich (added J code)
Task
Seven-sided dice from five-sided dice
You are encouraged to solve this task according to the task description, using any language you may know.

Given an equal-probability generator of one of the integers 1 to 5 as dice5; create dice7 that generates a pseudo-random integer from 1 to 7 in equal probability using only dice5 as a source of random numbers, and check the distribution for at least 1000000 calls using the function created in Simple Random Distribution Checker.

dice7 might call dice5 twice, re-call if four of the 25 combinations are given, otherwise split the other 21 combinations into 7 groups of three, and return the group index from the rolls.

(Task adapted from an answer here)

AutoHotkey

<lang AutoHotkey>dice5() { Random, v, 1, 5

  Return, v

}

dice7() { Loop

  {  v := 5 * dice5() + dice5() - 6
     IfLess v, 21, Return, (v // 3) + 1
  }

}</lang>

Distribution check:

Total elements = 10000

Margin = 3% --> Lbound = 1386, Ubound = 1471

Bucket 1 contains 1450 elements.
Bucket 2 contains 1374 elements. Skewed.
Bucket 3 contains 1412 elements.
Bucket 4 contains 1465 elements.
Bucket 5 contains 1370 elements. Skewed.
Bucket 6 contains 1485 elements. Skewed.
Bucket 7 contains 1444 elements.

C

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <math.h>

void distcheck(int (*)(), int, double);

int dice5() {

 return 1 + (int)(5.0*rand() / (RAND_MAX + 1.0));

}

int dice7() {

 int d55;
 do {
   d55 = 5*dice5() + dice5() - 6;
 } while(d55 >= 21);
 return d55 % 7  + 1;

}

int main() {

 distcheck(dice5, 1000000, 1);
 distcheck(dice7, 1000000, 1);
 return 0;

}</lang>

Common Lisp

Translation of: C

<lang lisp>(defun d5 ()

 (1+ (random 5)))

(defun d7 ()

 (loop for d55 = (+ (* 5 (d5)) (d5) -6)
       until (< d55 21)
       finally (return (1+ (mod d55 7)))))</lang>
> (check-distribution 'd7 1000)
Distribution potentially skewed for 1: expected around 1000/7 got 153.
Distribution potentially skewed for 2: expected around 1000/7 got 119.
Distribution potentially skewed for 3: expected around 1000/7 got 125.
Distribution potentially skewed for 7: expected around 1000/7 got 156.
T
#<EQL Hash Table{7} 200B5A53>

> (check-distribution 'd7 10000)
NIL
#<EQL Hash Table{7} 200CB5BB>

J

<lang J>

  dice5=: >:@?@(5&[)"0
  dice7=: 3 :'>.3%~5#.<:(-~ dice5^:(-.@*@{.))^:((>&21+<&1)@(5&#.@:<:))^:_ dice5 0 0[y'"0

</lang>

This is not the most elegant piece of code, but it does illustrate some points. One of them is that it is possible to use loops as expressions, not as statements. In the single statement for the dice7 function (called 'verb' in J) above there are 3 explicit loops, represented as ^: symbols.

OCaml

<lang ocaml>let dice5() = 1 + Random.int 5 ;;

let dice7 =

 let rolls2answer = Hashtbl.create 25 in
 let n = ref 0 in
 for roll1 = 1 to 5 do
   for roll2 = 1 to 5 do
     Hashtbl.add rolls2answer (roll1,roll2) (!n / 3 +1);
     incr n
   done;
 done;
 let rec aux() =
   let trial = Hashtbl.find rolls2answer (dice5(),dice5()) in
   if trial <= 7 then trial else aux()
 in
 aux
</lang>

Python

Follows the method suggested in the task description for creating dice7, and uses a function creator for dice7, to calculate the binning of the two calls to dice5. <lang python>import re, random

onetofive = (1,2,3,4,5)

def dice5():

   return random.choice(onetofive)

def dice7generator():

   rolls2answer = {}
   n=0
   for roll1 in onetofive:
       for roll2 in onetofive:
           rolls2answer[(roll1,roll2)] = (n // 3) + 1
           n += 1
   def dice7():
       'Generates 1 to 7 randomly, with equal prob. from dice5'
       trial = rolls2answer[(dice5(), dice5())]
       return trial if trial <=7 else dice7()
   return dice7

dice7 = dice7generator()</lang> Distribution check using Simple Random Distribution Checker:

>>> distcheck(dice5, 1000000, 1)
{1: 200244, 2: 199831, 3: 199548, 4: 199853, 5: 200524}
>>> distcheck(dice7, 1000000, 1)
{1: 142853, 2: 142576, 3: 143067, 4: 142149, 5: 143189, 6: 143285, 7: 142881}

R

5-sided die. <lang r> dice5 <- function(n=1) sample(5, n, replace=TRUE) </lang> Simple but slow 7-sided die, using a while loop. <lang r> dice7.while <- function(n=1) {

  score <- numeric()
  while(length(score) < n)
  {
     total <- sum(c(5,1) * dice5(2)) - 3
     if(total < 24) score <- c(score, total %/% 3)
  } 
  score 

} system.time(dice7.while(1e6)) # longer than 4 minutes </lang> More complex, but much faster vectorised version. <lang r> dice7.vec <- function(n=1, checkLength=TRUE) {

  morethan2n <- 3 * n + 10 + (n %% 2)       #need more than 2*n samples, because some are discarded
  twoDfive <- matrix(dice5(morethan2n), nrow=2)
  total <- colSums(c(5, 1) * twoDfive) - 3
  score <- ifelse(total < 24, total %/% 3, NA)
  score <- score[!is.na(score)]
  #If length is less than n (very unlikely), add some more samples
  if(checkLength) 
  {
     while(length(score) < n)
     {
        score <- c(score, dice7(n, FALSE)) 
     }
     score[1:n]
  } else score  

} system.time(dice7.vec(1e6)) # ~1 sec </lang>

Ruby

Translation of: Tcl

Uses distcheck from here. <lang ruby>require './distcheck.rb'

def d5

 1 + rand(5)

end

def d7

 loop do
   d55 = 5*d5() + d5() - 6
   return (d55 % 7 + 1) if d55 < 21
 end

end

distcheck(1_000_000) {d5} distcheck(1_000_000) {d7}</lang>

output

1 200478 2 199986 3 199582 4 199560 5 200394 
1 142371 2 142577 3 143328 4 143630 5 142553 6 142692 7 142849 

Tcl

Any old D&D hand will know these as a D5 and a D7... <lang tcl>proc D5 {} {expr {1 + int(5 * rand())}}

proc D7 {} {

   while 1 {
       set d55 [expr {5 * [D5] + [D5] - 6}]
       if {$d55 < 21} {
           return [expr {$d55 % 7 + 1}]
       }
   }

}</lang> Checking:

% distcheck D5 1000000
1 199893 2 200162 3 200075 4 199630 5 200240
% distcheck D7 1000000
1 143121 2 142383 3 143353 4 142811 5 142172 6 143291 7 142869