Task

(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 one million calls using the function created in   Simple Random Distribution Checker.


Implementation suggestion:
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)

import "random" for Random
import "/sort" for Sort
import "/fmt" for Fmt
 
var r = Random.new()
 
var dice5 = Fn.new { r.int(1, 6) }
 
var dice7 = Fn.new {
    while (true) {
        var t = (dice5.call() - 1) * 5 + dice5.call() - 1
        if (t < 21) return 1 + (t/3).floor
    }
}
 
var checkDist = Fn.new { |gen, nRepeats, tolerance|
    var occurs = {}
    for (i in 1..nRepeats) {
        var d = gen.call()
        occurs[d] = occurs.containsKey(d) ? occurs[d] + 1 : 1
    }
    var expected = (nRepeats/occurs.count).floor
    var maxError = (expected * tolerance / 100).floor
    System.print("Repetitions = %(nRepeats), Expected = %(expected)")
    System.print("Tolerance = %(tolerance)\%, Max Error = %(maxError)\n")
    System.print("Integer   Occurrences   Error  Acceptable")
    var f = "  $d        $5d      $5d     $s"
    var allAcceptable = true
    var cmp = Fn.new { |me1, me2| (me1.key - me2.key).sign }
    occurs = occurs.toList
    Sort.insertion(occurs, cmp)
    for (me in occurs) {
        var k = me.key
        var v = me.value
        var error = (v - expected).abs
        var acceptable = (error <= maxError) ? "Yes" : "No"
        if (acceptable == "No") allAcceptable = false
        Fmt.print(f, k, v, error, acceptable)
    }
    System.print("\nAcceptable overall: %(allAcceptable ? "Yes" : "No")")
}
 
checkDist.call(dice7, 1400000, 0.5)