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)
Ada
The specification of a package Random_57:
<lang Ada>package Random_57 is
type Mod_7 is mod 7;
function Random7 return Mod_7; -- a "fast" implementation, minimazing the calls to the Random5 generator function Simple_Random7 return Mod_7; -- a simple implementation
end Random_57;</lang>
Implementation of Random_57:
<lang Ada> with Ada.Numerics.Discrete_Random;
package body Random_57 is
type M5 is mod 5;
package Rand_5 is new Ada.Numerics.Discrete_Random(M5); Gen: Rand_5.Generator; function Random7 return Mod_7 is N: Natural;
begin loop N := Integer(Rand_5.Random(Gen))* 5 + Integer(Rand_5.Random(Gen)); -- N is uniformly distributed in 0 .. 24 if N < 21 then return Mod_7(N/3); else -- (N-21) is in 0 .. 3 N := (N-21) * 5 + Integer(Rand_5.Random(Gen)); -- N is in 0 .. 19 if N < 14 then return Mod_7(N / 2); else -- (N-14) is in 0 .. 5 N := (N-14) * 5 + Integer(Rand_5.Random(Gen)); -- N is in 0 .. 29 if N < 28 then return Mod_7(N/4); else -- (N-28) is in 0 .. 1 N := (N-28) * 5 + Integer(Rand_5.Random(Gen)); -- 0 .. 9 if N < 7 then return Mod_7(N); else -- (N-7) is in 0, 1, 2 N := (N-7)* 5 + Integer(Rand_5.Random(Gen)); -- 0 .. 14 if N < 14 then return Mod_7(N/2); else -- (N-14) is 0. This is not useful for us! null; end if; end if; end if; end if; end if; end loop;
end Random7;
function Simple_Random7 return Mod_7 is N: Natural := Integer(Rand_5.Random(Gen))* 5 + Integer(Rand_5.Random(Gen)); -- N is uniformly distributed in 0 .. 24 begin while N > 20 loop N := Integer(Rand_5.Random(Gen))* 5 + Integer(Rand_5.Random(Gen)); end loop; -- Now I <= 20 return Mod_7(N / 3); end Simple_Random7;
begin
Rand_5.Reset(Gen);
end Random_57;</lang>
A main program, using the Random_57 package:
<lang Ada>with Ada.Text_IO, Random_57;
procedure R57 is
use Random_57;
type Fun is access function return Mod_7;
function Rand return Mod_7 renames Random_57.Random7; -- change this to "... renames Random_57.Simple_Random;" if you like
procedure Test(Sample_Size: Positive; Rand: Fun; Precision: Float := 0.3) is
Counter: array(Mod_7) of Natural := (others => 0); Expected: Natural := Sample_Size/7; Small: Mod_7 := Mod_7'First; Large: Mod_7 := Mod_7'First;
Result: Mod_7; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line("Sample Size: " & Integer'Image(Sample_Size)); Ada.Text_IO.Put( " Bins:"); for I in 1 .. Sample_Size loop Result := Rand.all; Counter(Result) := Counter(Result) + 1; end loop; for J in Mod_7 loop Ada.Text_IO.Put(Integer'Image(Counter(J))); if Counter(J) < Counter(Small) then Small := J; end if; if Counter(J) > Counter(Large) then Large := J; end if; end loop; Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(" Small Bin:" & Integer'Image(Counter(Small))); Ada.Text_IO.Put_Line(" Large Bin: " & Integer'Image(Counter(Large)));
if Float(Counter(Small)*7) * (1.0+Precision) < Float(Sample_Size) then Ada.Text_IO.Put_Line("Failed! Small too small!"); elsif Float(Counter(Large)*7) * (1.0-Precision) > Float(Sample_Size) then Ada.Text_IO.Put_Line("Failed! Large too large!"); else Ada.Text_IO.Put_Line("Passed"); end if; end Test;
begin
Test( 10_000, Rand'Access, 0.08); Test( 100_000, Rand'Access, 0.04); Test( 1_000_000, Rand'Access, 0.02); Test(10_000_000, Rand'Access, 0.01);
end R57;</lang>
A sample output:
Sample Size: 10000 Bins: 1368 1404 1435 1491 1483 1440 1379 Small Bin: 1368 Large Bin: 1491 Passed Sample Size: 100000 Bins: 14385 14110 14362 14404 14362 14206 14171 Small Bin: 14110 Large Bin: 14404 Passed Sample Size: 1000000 Bins: 143765 142384 142958 142684 142799 142956 142454 Small Bin: 142384 Large Bin: 143765 Passed Sample Size: 10000000 Bins: 1429266 1428214 1428753 1427032 1428418 1428699 1429618 Small Bin: 1427032 Large Bin: 1429618 Passed
ALGOL 68
- note: This specimen retains the original C coding style.
C's version using no multiplications, divisions, or mod operators: <lang algol68>PROC dice5 = INT:
1 + ENTIER (5*random);
PROC mulby5 = (INT n)INT:
ABS (BIN n SHL 2) + n;
PROC dice7 = INT: (
INT d55 := 0; INT m := 1; WHILE m := ABS ((2r1 AND BIN m) SHL 2) + ABS (BIN m SHR 1); # repeats 4 - 2 - 1 # d55 := mulby5(mulby5(d55)) + mulby5(dice5) + dice5 - 6;
- WHILE # d55 < m DO SKIP OD;
m := 1; WHILE d55>0 DO d55 +:= m; m := ABS (BIN d55 AND 2r111); # modulas by 8 # d55 := ABS (BIN d55 SHR 3) # divide by 8 # OD; m
);
PROC distcheck = (PROC INT dice, INT count, upb)VOID: (
[upb]INT sum; FOR i TO UPB sum DO sum[i] := 0 OD; FOR i TO count DO sum[dice]+:=1 OD; FOR i TO UPB sum WHILE print(whole(sum[i],0)); i /= UPB sum DO print(", ") OD; print(new line)
);
main: (
distcheck(dice5, 1000000, 5); distcheck(dice7, 1000000, 7)
)</lang> Sample output:
200598, 199852, 199939, 200602, 199009 143529, 142688, 142816, 142747, 142958, 142802, 142460
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>int rand5() { int r, rand_max = RAND_MAX - (RAND_MAX % 5); while ((r = rand()) >= rand_max); return r / (rand_max / 5) + 1; }
int rand5_7() { int r; while ((r = rand5() * 5 + rand5()) >= 27); return r / 3 - 1; }
int main() { printf(check(rand5, 5, 1000000, .05) ? "flat\n" : "not flat\n"); printf(check(rand7, 7, 1000000, .05) ? "flat\n" : "not flat\n"); return 0;
}</lang>output
flat flat
C++
This solution tries to minimize calls to the underlying d5 by reusing information from earlier calls.
<lang cpp>template<typename F> class fivetoseven { public:
fivetoseven(F f): d5(f), rem(0), max(1) {} int operator()();
private:
F d5; int rem, max;
};
template<typename F>
int fivetoseven<F>::operator()()
{
while (rem/7 == max/7) { while (max < 7) { int rand5 = d5()-1; max *= 5; rem = 5*rem + rand5; }
int groups = max / 7; if (rem >= 7*groups) { rem -= 7*groups; max -= 7*groups; } }
int result = rem % 7; rem /= 7; max /= 7; return result+1;
}
int d5() {
return 5.0*std::rand()/(RAND_MAX + 1.0) + 1;
}
fivetoseven<int(*)()> d7(d5);
int main() {
srand(time(0)); test_distribution(d5, 1000000, 0.001); test_distribution(d7, 1000000, 0.001);
}</lang>
Clojure
Uses the verify function defined in http://rosettacode.org/wiki/Simple_Random_Distribution_Checker#Clojure <lang Clojure>(def dice5 #(rand-int 5))
(defn dice7 []
(quot (->> dice5 ; do the following to dice5 (repeatedly 2) ; call it twice (apply #(+ %1 (* 5 %2))) ; d1 + 5*d2 => 0..24 #() ; wrap that up in a function repeatedly ; make infinite sequence of the above (drop-while #(> % 20)) ; throw away anything > 20 first) ; grab first acceptable element 3)) ; divide by three rounding down
(doseq [n [100 1000 10000] [num count okay?] (verify dice7 n)]
(println "Saw" num count "times:" (if okay? "that's" " not") "acceptable"))</lang>
Saw 0 10 times: not acceptable Saw 1 19 times: not acceptable Saw 2 12 times: not acceptable Saw 3 15 times: that's acceptable Saw 4 11 times: not acceptable Saw 5 11 times: not acceptable Saw 6 22 times: not acceptable Saw 0 142 times: that's acceptable Saw 1 158 times: not acceptable Saw 2 151 times: that's acceptable Saw 3 153 times: that's acceptable Saw 4 118 times: not acceptable Saw 5 139 times: that's acceptable Saw 6 139 times: that's acceptable Saw 0 1498 times: that's acceptable Saw 1 1411 times: that's acceptable Saw 2 1436 times: that's acceptable Saw 3 1434 times: that's acceptable Saw 4 1414 times: that's acceptable Saw 5 1408 times: that's acceptable Saw 6 1399 times: that's acceptable
Common Lisp
<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>
D
<lang d>import std.random: rand; import distcheck: distCheck;
/// generates a random number in [1, 5] int dice5() {
return 1 + cast(int)((rand() / cast(double)uint.max) * 5.0);
}
/// Naive version, generates a random number in [1, 7] using dice5
int dice7() {
int r = dice5() + dice5() * 5 - 6; return (r < 21) ? (r % 7) + 1 : dice7();
}
/**
Generates a random number in [1, 7] using dice5,
minimizing calls to dice 5.
- /
struct FiveToSeven(alias d5) {
int opCall() { while (rem / 7 == max / 7) { while (max < 7) { int rand5 = d5() - 1; max *= 5; rem = 5 * rem + rand5; }
int groups = max / 7; if (rem >= 7 * groups) { rem -= 7 * groups; max -= 7 * groups; } }
int result = rem % 7; rem /= 7; max /= 7; return result + 1; }
private: int rem = 0, max = 1;
}
void main() {
const int N = 1_000_000; distCheck(&dice5, N, 1); distCheck(&dice7, N, 1); // naive version FiveToSeven!(dice5) dice7b; distCheck(&dice7b.opCall, N, 1);
}</lang>
Output: [1:200416,2:199418,3:199471,4:200016,5:200679] [1:142443,2:143605,3:142878,4:143038,5:142595,6:142205,7:143236] [1:143354,2:143240,3:142882,4:142641,5:142715,6:142779,7:142389]
E
<lang e>def dice5() {
return entropy.nextInt(5) + 1
}
def dice7() {
var d55 := null while ((d55 := 5 * dice5() + dice5() - 6) >= 21) {} return d55 %% 7 + 1
}</lang>
<lang e>def bins := ([0] * 7).diverge() for x in 1..1000 {
bins[dice7() - 1] += 1
} println(bins.snapshot())</lang>
Fortran
<lang fortran>module rand_mod
implicit none
contains
function rand5()
integer :: rand5 real :: r
call random_number(r) rand5 = 5*r + 1
end function
function rand7()
integer :: rand7 do rand7 = 5*rand5() + rand5() - 6 if (rand7 < 21) then rand7 = rand7 / 3 + 1 return end if end do
end function end module
program Randtest
use rand_mod implicit none integer, parameter :: samples = 1000000 call distcheck(rand7, samples, 0.005) write(*,*) call distcheck(rand7, samples, 0.001)
end program</lang> Output
Distribution Uniform Distribution potentially skewed for bucket 1 Expected: 142857 Actual: 143142 Distribution potentially skewed for bucket 2 Expected: 142857 Actual: 143454 Distribution potentially skewed for bucket 3 Expected: 142857 Actual: 143540 Distribution potentially skewed for bucket 4 Expected: 142857 Actual: 142677 Distribution potentially skewed for bucket 5 Expected: 142857 Actual: 142511 Distribution potentially skewed for bucket 6 Expected: 142857 Actual: 142163 Distribution potentially skewed for bucket 7 Expected: 142857 Actual: 142513
Go
<lang go>package main
import (
"fmt" "math" "math/rand" "time"
)
// "given" func dice5() int {
return rand.Intn(5) + 1
}
// function specified by task "Seven-sided dice from five-sided dice" func dice7() (i int) {
for { i = 5*dice5() + dice5() if i < 27 { break } } return (i / 3) - 1
}
// function specified by task "Verify distribution uniformity/Naive" // // Parameter "f" is expected to return a random integer in the range 1..n. // (Values out of range will cause an unceremonious crash.) // "Max" is returned as an "indication of distribution achieved." // It is the maximum delta observed from the count representing a perfectly // uniform distribution. // Also returned is a boolean, true if "max" is less than threshold // parameter "delta." func distCheck(f func() int, n int,
repeats int, delta float64) (max float64, flatEnough bool) { count := make([]int, n) for i := 0; i < repeats; i++ { count[f()-1]++ } expected := float64(repeats) / float64(n) for _, c := range count { max = math.Max(max, math.Abs(float64(c)-expected)) } return max, max < delta
}
// Driver, produces output satisfying both tasks. func main() {
rand.Seed(time.Now().UnixNano()) const calls = 1000000 max, flatEnough := distCheck(dice7, 7, calls, 500) fmt.Println("Max delta:", max, "Flat enough:", flatEnough) max, flatEnough = distCheck(dice7, 7, calls, 500) fmt.Println("Max delta:", max, "Flat enough:", flatEnough)
}</lang> Output:
Max delta: 356.1428571428696 Flat enough: true Max delta: 787.8571428571304 Flat enough: false
Groovy
Solution: <lang groovy>random = new Random()
int rand5() {
random.nextInt(5) + 1
}
int rand7From5() {
def raw = 25 while (raw > 21) { raw = 5*(rand5() - 1) + rand5() } (raw % 7) + 1
}</lang>
Test: <lang groovy>def test = {
(1..6). each { def counts = [0g, 0g, 0g, 0g, 0g, 0g, 0g] def target = 10g**it def popSize = 7*target (0..<(popSize)).each { def i = rand7From5() - 1 counts[i] = counts[i] + 1g } BigDecimal stdDev = (counts.collect { it - target}.collect { it * it }.sum() / popSize) ** 0.5g def countMap = (0..<counts.size()).inject([:]) { map, index -> map + [(index+1):counts[index]] } println """\ counts: ${countMap}
population size: ${popSize}
std dev: ${stdDev.round(new java.math.MathContext(3))}
"""
}
}
4.times {
println """
TRIAL #${it+1} =============="""
test(it)
}</lang>
Output:
TRIAL #1 ============== counts: [1:16, 2:10, 3:9, 4:7, 5:12, 6:8, 7:8] population size: 70 std dev: 0.910 counts: [1:85, 2:97, 3:108, 4:110, 5:95, 6:105, 7:100] population size: 700 std dev: 0.800 counts: [1:990, 2:1008, 3:992, 4:1060, 5:1008, 6:997, 7:945] population size: 7000 std dev: 0.995 counts: [1:9976, 2:10007, 3:10009, 4:9858, 5:10109, 6:9988, 7:10053] population size: 70000 std dev: 0.714 counts: [1:100310, 2:99783, 3:99843, 4:100353, 5:99804, 6:99553, 7:100354] population size: 700000 std dev: 0.968 counts: [1:999320, 2:1000942, 3:1000201, 4:1000878, 5:999181, 6:999632, 7:999846] population size: 7000000 std dev: 0.654 TRIAL #2 ============== counts: [1:10, 2:8, 3:9, 4:9, 5:14, 6:7, 7:13] population size: 70 std dev: 0.756 counts: [1:104, 2:101, 3:97, 4:108, 5:100, 6:87, 7:103] population size: 700 std dev: 0.619 counts: [1:995, 2:970, 3:1001, 4:953, 5:1006, 6:1081, 7:994] population size: 7000 std dev: 1.18 counts: [1:10013, 2:10063, 3:9843, 4:9984, 5:9986, 6:10059, 7:10052] population size: 70000 std dev: 0.711 counts: [1:100048, 2:99647, 3:100240, 4:100683, 5:99813, 6:100320, 7:99249] population size: 700000 std dev: 1.39 counts: [1:1000579, 2:1000541, 3:999497, 4:1000805, 5:999708, 6:999161, 7:999709] population size: 7000000 std dev: 0.586 TRIAL #3 ============== counts: [1:9, 2:8, 3:11, 4:14, 5:10, 6:11, 7:7] population size: 70 std dev: 0.676 counts: [1:100, 2:92, 3:105, 4:107, 5:111, 6:91, 7:94] population size: 700 std dev: 0.733 counts: [1:1010, 2:1053, 3:967, 4:981, 5:1027, 6:959, 7:1003] population size: 7000 std dev: 0.984 counts: [1:9857, 2:10037, 3:9992, 4:10231, 5:9828, 6:10140, 7:9915] population size: 70000 std dev: 1.37 counts: [1:99650, 2:99580, 3:99848, 4:100507, 5:99916, 6:100212, 7:100287] population size: 700000 std dev: 1.01 counts: [1:1001710, 2:999667, 3:1000685, 4:1000411, 5:999369, 6:998469, 7:999689] population size: 7000000 std dev: 0.965 TRIAL #4 ============== counts: [1:12, 2:7, 3:11, 4:12, 5:7, 6:9, 7:12] population size: 70 std dev: 0.676 counts: [1:97, 2:96, 3:101, 4:93, 5:96, 6:124, 7:93] population size: 700 std dev: 1.01 counts: [1:985, 2:1023, 3:1018, 4:1023, 5:995, 6:973, 7:983] population size: 7000 std dev: 0.615 counts: [1:9948, 2:9968, 3:10131, 4:10050, 5:9990, 6:10039, 7:9874] population size: 70000 std dev: 0.764 counts: [1:100125, 2:99616, 3:99912, 4:100286, 5:99674, 6:100190, 7:100197] population size: 700000 std dev: 0.787 counts: [1:1001267, 2:999911, 3:1000602, 4:999483, 5:1000549, 6:998725, 7:999463] population size: 7000000 std dev: 0.798
Haskell
<lang haskell>import System.Random import Data.List
sevenFrom5Dice = do
d51 <- randomRIO(1,5) :: IO Int d52 <- randomRIO(1,5) :: IO Int let d7 = 5*d51+d52-6 if d7 > 20 then sevenFrom5Dice else return $ 1 + d7 `mod` 7</lang>
Output: <lang haskell>*Main> replicateM 10 sevenFrom5Dice [2,3,1,1,6,2,5,6,5,3]</lang> Test: <lang haskell>*Main> mapM_ print .sort =<< distribCheck sevenFrom5Dice 1000000 3 (1,(142759,True)) (2,(143078,True)) (3,(142706,True)) (4,(142403,True)) (5,(142896,True)) (6,(143028,True)) (7,(143130,True))</lang>
Icon and Unicon
Uses verify_uniform
from here.
<lang Icon> $include "distribution-checker.icn"
- return a uniformly distributed number from 1 to 7,
- but only using a random number in range 1 to 5.
procedure die_7 ()
while rnd := 5*?5 + ?5 - 6 do { if rnd < 21 then suspend rnd % 7 + 1 }
end
procedure main ()
if verify_uniform (create (|die_7()), 1000000, 0.01) then write ("uniform") else write ("skewed")
end </lang>
Output:
5 142870 2 142812 7 142901 4 142960 1 143113 6 142706 3 142638 uniform
J
The first step is to create 7-sided dice rolls from 5-sided dice rolls (rollD5
):
<lang j>rollD5=: [: >: ] ?@$ 5: NB. makes a y shape array of 5s, "rolls" the array and increments.
roll2xD5=: [: rollD5 2 ,~ */ NB. rolls D5 twice for each desired D7 roll (y rows, 2 cols)
toBase10=: 5 #. <: NB. decrements and converts rows from base 5 to 10
keepGood=: #~ 21&> NB. compress out values not less than 21
groupin3s=: [: >. >: % 3: NB. increments, divides by 3 and takes ceiling
getD7=: groupin3s@keepGood@toBase10@roll2xD5</lang>
Here are a couple of variations on the theme that achieve the same result: <lang j>getD7b=: 0 8 -.~ 3 >.@%~ 5 #. [: <:@rollD5 2 ,~ ] getD7c=: [: (#~ 7&>:) 3 >.@%~ [: 5&#.&.:<:@rollD5 ] , 2:</lang>
The trouble is that we probably don't have enough D7 rolls yet because we compressed out any double D5 rolls that evaluated to 21 or more. So we need to accumulate some more D7 rolls until we have enough. J has two types of verb definition - tacit (arguments not referenced) and explicit (more conventional function definitions) illustrated below:
Here's an explicit definition that accumulates rolls from getD7
:
<lang j>rollD7x=: monad define
n=. */y NB. product of vector y is total number of D7 rolls required rolls=. NB. initialize empty noun rolls while. n > #res do. NB. checks if if enough D7 rolls accumulated rolls=. rolls, getD7 >. 0.75 * n NB. calcs 3/4 of required rolls and accumulates getD7 rolls end. y $ rolls NB. shape the result according to the vector y
)</lang>
Here's a tacit definition that does the same thing: <lang j>getNumRolls=: [: >. 0.75 * */@[ NB. calc approx 3/4 of the required rolls accumD7Rolls=: ] , getD7@getNumRolls NB. accumulates getD7 rolls isNotEnough=: */@[ > #@] NB. checks if enough D7 rolls accumulated
rollD7t=: ] $ (accumD7Rolls ^: isNotEnough ^:_)&</lang>
The verb1 ^: verb2 ^:_
construct repeats x verb1 y
while x verb2 y
is true. It is like saying "Repeat accumD7Rolls while isNotEnough".
Example usage: <lang j> rollD7t 10 NB. 10 rolls of D7 6 4 5 1 4 2 4 5 2 5
rollD7t 2 5 NB. 2 by 5 array of D7 rolls
5 1 5 1 3 3 4 3 5 6
rollD7t 2 3 5 NB. 2 by 3 by 5 array of D7 rolls
4 7 7 5 7 3 7 1 4 5 5 4 5 7 6
1 1 7 6 3 4 4 1 4 4 1 1 1 6 5
NB. check results from rollD7x and rollD7t have same shape
($@rollD7x -: $@rollD7t) 10
1
($@rollD7x -: $@rollD7t) 2 3 5
1</lang>
JavaScript
<lang javascript>function dice5() {
return 1 + Math.floor(5 * Math.random());
}
function dice7() {
while (true) { var dice55 = 5 * dice5() + dice5() - 6; if (dice55 < 21) return dice55 % 7 + 1; }
}
distcheck(dice5, 1000000); print(); distcheck(dice7, 1000000);</lang> output
1 199792 2 200425 3 199243 4 200407 5 200133 1 143617 2 142209 3 143023 4 142990 5 142894 6 142648 7 142619
Lua
<lang lua>dice5 = function() return math.random(5) end
function dice7()
x = dice5() * 5 + dice5() - 6 if x > 20 then return dice7() end return x%7 + 1
end</lang>
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>
PARI/GP
<lang parigp>dice5()=random(5)+1;
dice7()={
my(t); while((t=dice5()*5+dice5()) > 21,); (t+2)\3
};</lang>
Perl 6
Since rakudo is still pretty slow, we've done some interesting bits of optimization.
We factor out the range object construction so that it doesn't have to be recreated each time, and we sneakily subtract the 1's from the 5's, which takes us back to 0 based without having to subtract 6.
<lang perl6>my $d5 = 1..5;
sub d5() { $d5.roll; } # 1d5
sub d7() {
my $flat = 21;
$flat = 5 * d5() - d5() until $flat < 21;
$flat % 7 + 1;
}</lang>
Here's the test. We use a C-style for loop, except it's named loop
, because it's currently faster than the other loops--and, er, doesn't segfault the GC on a million iterations...
<lang perl6>my @dist;
my $n = 1_000_000;
my $expect = $n / 7;
loop ($_ = $n; $n; --$n) { @dist[d7()]++; }
say "Expect\t",$expect.fmt("%.3f");
for @dist.kv -> $i, $v {
say "$i\t$v\t" ~ (($v - $expect)/$expect*100).fmt("%+.2f%%") if $v;
}</lang>
And the output:
<lang>Expect 142857.143
1 142835 -0.02%
2 143021 +0.11%
3 142771 -0.06%
4 142706 -0.11%
5 143258 +0.28%
6 142485 -0.26%
7 142924 +0.05%</lang>
PicoLisp
<lang PicoLisp>(de dice5 ()
(rand 1 5) )
(de dice7 ()
(use R (until (> 21 (setq R (+ (* 5 (dice5)) (dice5) -6)))) (inc (% R 7)) ) )</lang>
Output:
: (let R NIL (do 1000000 (accu 'R (dice7) 1)) (sort R) ) -> ((1 . 142295) (2 . 142491) (3 . 143448) (4 . 143129) (5 . 142701) (6 . 143142) (7 . 142794))
PureBasic
<lang PureBasic>Procedure dice5()
ProcedureReturn Random(4) + 1
EndProcedure
Procedure dice7()
Protected x x = dice5() * 5 + dice5() - 6 If x > 20 ProcedureReturn dice7() EndIf ProcedureReturn x % 7 + 1
EndProcedure</lang>
Python
<lang python>from random import randint
def dice5():
return randint(1, 5)
def dice7():
r = dice5() + dice5() * 5 - 6 return (r % 7) + 1 if r < 21 else dice7()</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}
REXX
<lang rexx> /*REXX program to simulate 7-sided die base on a 5-sided throw. */
arg trials samp . if trails== then trials=1 if samp== then samp=1000000
do t=1 for trials die.=0 k=0
do until k==samp r=5*random(1,5)+random(1,5)-6 if r>20 then iterate k=k+1 r=r//7+1 die.r=die.r+1 end /*do until*/
expect=trunc(samp/7) say say '───────────────trial:'right(t,4) ' ' samp 'samples, expect='expect say
do j=1 for 7 say 'side' j "had" die.j 'occurances', ' difference from expected='right(die.j-expect,length(samp)) end
end /*t*/
</lang>
Output when the following input is specified:
11
───────────────trial: 1 1000000 samples, expect=142857 side 1 had 143396 occurances difference from expected= 539 side 2 had 142920 occurances difference from expected= 63 side 3 had 142915 occurances difference from expected= 58 side 4 had 143220 occurances difference from expected= 363 side 5 had 142297 occurances difference from expected= -560 side 6 had 142210 occurances difference from expected= -647 side 7 had 143042 occurances difference from expected= 185 ───────────────trial: 2 1000000 samples, expect=142857 side 1 had 142853 occurances difference from expected= -4 side 2 had 142889 occurances difference from expected= 32 side 3 had 142800 occurances difference from expected= -57 side 4 had 142801 occurances difference from expected= -56 side 5 had 143153 occurances difference from expected= 296 side 6 had 143010 occurances difference from expected= 153 side 7 had 142494 occurances difference from expected= -363 ───────────────trial: 3 1000000 samples, expect=142857 side 1 had 141894 occurances difference from expected= -963 side 2 had 143035 occurances difference from expected= 178 side 3 had 142894 occurances difference from expected= 37 side 4 had 143045 occurances difference from expected= 188 side 5 had 142817 occurances difference from expected= -40 side 6 had 143209 occurances difference from expected= 352 side 7 had 143106 occurances difference from expected= 249 ───────────────trial: 4 1000000 samples, expect=142857 side 1 had 142515 occurances difference from expected= -342 side 2 had 142943 occurances difference from expected= 86 side 3 had 143083 occurances difference from expected= 226 side 4 had 142912 occurances difference from expected= 55 side 5 had 142985 occurances difference from expected= 128 side 6 had 142928 occurances difference from expected= 71 side 7 had 142634 occurances difference from expected= -223 ───────────────trial: 5 1000000 samples, expect=142857 side 1 had 142770 occurances difference from expected= -87 side 2 had 143373 occurances difference from expected= 516 side 3 had 142253 occurances difference from expected= -604 side 4 had 142884 occurances difference from expected= 27 side 5 had 142885 occurances difference from expected= 28 side 6 had 142942 occurances difference from expected= 85 side 7 had 142893 occurances difference from expected= 36 ───────────────trial: 6 1000000 samples, expect=142857 side 1 had 143026 occurances difference from expected= 169 side 2 had 143530 occurances difference from expected= 673 side 3 had 142618 occurances difference from expected= -239 side 4 had 142573 occurances difference from expected= -284 side 5 had 142704 occurances difference from expected= -153 side 6 had 142949 occurances difference from expected= 92 side 7 had 142600 occurances difference from expected= -257 ───────────────trial: 7 1000000 samples, expect=142857 side 1 had 142253 occurances difference from expected= -604 side 2 had 143807 occurances difference from expected= 950 side 3 had 141837 occurances difference from expected= -1020 side 4 had 143513 occurances difference from expected= 656 side 5 had 142523 occurances difference from expected= -334 side 6 had 142936 occurances difference from expected= 79 side 7 had 143131 occurances difference from expected= 274 ───────────────trial: 8 1000000 samples, expect=142857 side 1 had 143083 occurances difference from expected= 226 side 2 had 143202 occurances difference from expected= 345 side 3 had 142115 occurances difference from expected= -742 side 4 had 143778 occurances difference from expected= 921 side 5 had 142780 occurances difference from expected= -77 side 6 had 142519 occurances difference from expected= -338 side 7 had 142523 occurances difference from expected= -334 ───────────────trial: 9 1000000 samples, expect=142857 side 1 had 143292 occurances difference from expected= 435 side 2 had 142688 occurances difference from expected= -169 side 3 had 142617 occurances difference from expected= -240 side 4 had 142942 occurances difference from expected= 85 side 5 had 142675 occurances difference from expected= -182 side 6 had 143216 occurances difference from expected= 359 side 7 had 142570 occurances difference from expected= -287 ───────────────trial: 10 1000000 samples, expect=142857 side 1 had 143387 occurances difference from expected= 530 side 2 had 142934 occurances difference from expected= 77 side 3 had 142650 occurances difference from expected= -207 side 4 had 142560 occurances difference from expected= -297 side 5 had 142708 occurances difference from expected= -149 side 6 had 142665 occurances difference from expected= -192 side 7 had 143096 occurances difference from expected= 239 ───────────────trial: 11 1000000 samples, expect=142857 side 1 had 142608 occurances difference from expected= -249 side 2 had 142583 occurances difference from expected= -274 side 3 had 142888 occurances difference from expected= 31 side 4 had 143286 occurances difference from expected= 429 side 5 had 142864 occurances difference from expected= 7 side 6 had 142971 occurances difference from expected= 114 side 7 had 142800 occurances difference from expected= -57
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
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
VBScript
<lang vb>option explicit
dim rolls rolls = "11,12,13,14,15,21,22,23,24,25,31,32,33,34,35,41,42,43,44,45,51,"
randomize timer
function irandom(n) irandom = int(rnd*n)+1 end function
function d7() dim j do j = instr( rolls, irandom(5) & irandom(5) ) if j <> 0 then d7 = (((j-1)/3) mod 7)+1 exit function end if loop end function</lang>