This commit is contained in:
Ingy döt Net 2013-04-10 21:29:02 -07:00
parent 764da6cbbb
commit db842d013d
19005 changed files with 197040 additions and 7 deletions

View file

@ -0,0 +1,23 @@
This is a method of randomly sampling n items from a set of M items, with equal probability; where M >= n and M, the number of items is unknown until the end.
This means that the equal probability sampling should be maintained for all successive items > n as they become available (although the content of successive samples can change).
;The algorithm:
# Select the first n items as the sample as they become available;
# For the i-th item where i > n, have a random chance of n/i of keeping it. If failing this chance, the sample remains the same. If not, have it randomly (1/n) replace one of the previously selected n items of the sample.
# Repeat #2 for any subsequent items.
;The Task:
# Create a function <code>s_of_n_creator</code> that given <math>n</math> the maximum sample size, returns a function <code>s_of_n</code> that takes one parameter, <code>item</code>.
# Function <code>s_of_n</code> when called with successive items returns an equi-weighted random sample of up to n of its items so far, each time it is called, calculated using Knuths Algorithm S.
# Test your functions by printing and showing the frequency of occurrences of the selected digits from 100,000 repetitions of:
:# Use the s_of_n_creator with n == 3 to generate an s_of_n.
:# call s_of_n with each of the digits 0 to 9 in order, keeping the returned three digits of its random sampling from its last call with argument item=9.
Note: A class taking n and generating a callable instance/function might also be used.
;Reference:
* The Art of Computer Programming, Vol 2, 3.4.2 p.142
;Cf.
* [[One of n lines in a file]]
* [[Accumulator factory]]

View file

@ -0,0 +1,12 @@
generic
Sample_Size: Positive;
type Item_Type is private;
package S_Of_N_Creator is
subtype Index_Type is Positive range 1 .. Sample_Size;
type Item_Array is array (Index_Type) of Item_Type;
procedure Update(New_Item: Item_Type);
function Result return Item_Array;
end S_Of_N_Creator;

View file

@ -0,0 +1,38 @@
with Ada.Numerics.Float_Random, Ada.Numerics.Discrete_Random;
package body S_Of_N_Creator is
package F_Rnd renames Ada.Numerics.Float_Random;
F_Gen: F_Rnd.Generator;
package D_Rnd is new Ada.Numerics.Discrete_Random(Index_Type);
D_Gen: D_Rnd.Generator;
Item_Count: Natural := 0; -- this is a global counter
Sample: Item_Array; -- also used globally
procedure Update(New_Item: Item_Type) is
begin
Item_Count := Item_Count + 1;
if Item_Count <= Sample_Size then
-- select the first Sample_Size items as the sample
Sample(Item_Count) := New_Item;
else
-- for I-th item, I > Sample_Size: Sample_Size/I chance of keeping it
if (Float(Sample_Size)/Float(Item_Count)) > F_Rnd.Random(F_Gen) then
-- randomly (1/Sample_Size) replace one of the items of the sample
Sample(D_Rnd.Random(D_Gen)) := New_Item;
end if;
end if;
end Update;
function Result return Item_Array is
begin
Item_Count := 0; -- ready to start another run
return Sample;
end Result;
begin
D_Rnd.Reset(D_Gen); -- at package instantiation, initialize rnd-generators
F_Rnd.Reset(F_Gen);
end S_Of_N_Creator;

View file

@ -0,0 +1,34 @@
with S_Of_N_Creator, Ada.Text_IO;
procedure Test_S_Of_N is
Repetitions: constant Positive := 100_000;
type D_10 is range 0 .. 9;
-- the instantiation of the generic package S_Of_N_Creator generates
-- a package with the desired functionality
package S_Of_3 is new S_Of_N_Creator(Sample_Size => 3, Item_Type => D_10);
Sample: S_Of_3.Item_Array;
Result: array(D_10) of Natural := (others => 0);
begin
for J in 1 .. Repetitions loop
-- get Sample
for Dig in D_10 loop
S_Of_3.Update(Dig);
end loop;
Sample := S_Of_3.Result;
-- update current Result
for Item in Sample'Range loop
Result(Sample(Item)) := Result(Sample(Item)) + 1;
end loop;
end loop;
-- finally: output Result
for Dig in Result'Range loop
Ada.Text_IO.Put(D_10'Image(Dig) & ":"
& Natural'Image(Result(Dig)) & "; ");
end loop;
end Test_S_Of_N;

View file

@ -0,0 +1,54 @@
HIMEM = PAGE + 20000000
PRINT "Single run samples for n = 3:"
SofN% = FNs_of_n_creator(3)
FOR I% = 0 TO 9
!^a%() = FN(SofN%)(I%)
PRINT " For item " ; I% " sample(s) = " FNshowarray(a%(), I%+1)
NEXT
DIM cnt%(9)
PRINT '"Digit counts after 100000 runs:"
FOR rep% = 1 TO 100000
IF (rep% MOD 1000) = 0 PRINT ; rep% ; CHR$(13) ;
F% = FNs_of_n_creator(3)
FOR I% = 0 TO 9
!^a%() = FN(F%)(I%)
NEXT
cnt%(a%(1)) += 1 : cnt%(a%(2)) += 1 : cnt%(a%(3)) += 1
NEXT
FOR digit% = 0 TO 9
PRINT " " ; digit% " : " ; cnt%(digit%)
NEXT
END
REM Dynamically creates this function:
REM DEF FNfunction(item%) : PRIVATE samples%(), index%
REM DIM samples%(n%) : = FNs_of_n(item%, samples%(), index%)
DEF FNs_of_n_creator(n%)
LOCAL p%, f$
f$ = "(item%) : " + CHR$&0E + " samples%(), index% : " + \
\ CHR$&DE + " samples%(" + STR$(n%) + ") : = " + \
\ CHR$&A4 + "s_of_n(item%, samples%(), index%)"
DIM p% LEN(f$) + 4 : $(p%+4) = f$ : !p% = p%+4
= p%
DEF FNs_of_n(D%, s%(), RETURN I%)
LOCAL N%
N% = DIM(s%(),1)
I% += 1
IF I% <= N% THEN
s%(I%) = D%
ELSE
IF RND(I%) <= N% s%(RND(N%)) = D%
ENDIF
= !^s%()
DEF FNshowarray(a%(), n%)
LOCAL i%, a$
a$ = "["
IF n% > DIM(a%(),1) n% = DIM(a%(),1)
FOR i% = 1 TO n%
a$ += STR$(a%(i%)) + ", "
NEXT
= LEFT$(LEFT$(a$)) + "]"

View file

@ -0,0 +1,36 @@
#include <iostream>
#include <functional>
#include <vector>
#include <cstdlib>
#include <ctime>
template <typename T>
std::function<std::vector<T>(T)> s_of_n_creator(int n) {
std::vector<T> sample;
int i = 0;
return [=](T item) mutable {
i++;
if (i <= n) {
sample.push_back(item);
} else if (std::rand() % i < n) {
sample[std::rand() % n] = item;
}
return sample;
};
}
int main() {
std::srand(std::time(NULL));
int bin[10] = {0};
for (int trial = 0; trial < 100000; trial++) {
auto s_of_n = s_of_n_creator<int>(3);
std::vector<int> sample;
for (int i = 0; i < 10; i++)
sample = s_of_n(i);
for (int s : sample)
bin[s]++;
}
for (int x : bin)
std::cout << x << std::endl;
return 0;
}

View file

@ -0,0 +1,38 @@
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
template <typename T>
class SOfN {
std::vector<T> sample;
int i;
const int n;
public:
SOfN(int _n) : i(0), n(_n) { }
std::vector<T> operator()(T item) {
i++;
if (i <= n) {
sample.push_back(item);
} else if (std::rand() % i < n) {
sample[std::rand() % n] = item;
}
return sample;
}
};
int main() {
std::srand(std::time(NULL));
int bin[10] = {0};
for (int trial = 0; trial < 100000; trial++) {
SOfN<int> s_of_n(3);
std::vector<int> sample;
for (int i = 0; i < 10; i++)
sample = s_of_n(i);
for (std::vector<int>::const_iterator i = sample.begin(); i != sample.end(); i++)
bin[*i]++;
}
for (int i = 0; i < 10; i++)
std::cout << bin[i] << std::endl;
return 0;
}

View file

@ -0,0 +1,72 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
struct s_env {
unsigned int n, i;
size_t size;
void *sample;
};
void s_of_n_init(struct s_env *s_env, size_t size, unsigned int n)
{
s_env->i = 0;
s_env->n = n;
s_env->size = size;
s_env->sample = malloc(n * size);
}
void sample_set_i(struct s_env *s_env, unsigned int i, void *item)
{
memcpy(s_env->sample + i * s_env->size, item, s_env->size);
}
void *s_of_n(struct s_env *s_env, void *item)
{
s_env->i++;
if (s_env->i <= s_env->n)
sample_set_i(s_env, s_env->i - 1, item);
else if ((rand() % s_env->i) < s_env->n)
sample_set_i(s_env, rand() % s_env->n, item);
return s_env->sample;
}
int *test(unsigned int n, int *items_set, unsigned int num_items)
{
int i;
struct s_env s_env;
s_of_n_init(&s_env, sizeof(items_set[0]), n);
for (i = 0; i < num_items; i++) {
s_of_n(&s_env, (void *) &items_set[i]);
}
return (int *)s_env.sample;
}
int main()
{
unsigned int i, j;
unsigned int n = 3;
unsigned int num_items = 10;
unsigned int *frequencies;
int *items_set;
srand(time(NULL));
items_set = malloc(num_items * sizeof(int));
frequencies = malloc(num_items * sizeof(int));
for (i = 0; i < num_items; i++) {
items_set[i] = i;
frequencies[i] = 0;
}
for (i = 0; i < 100000; i++) {
int *res = test(n, items_set, num_items);
for (j = 0; j < n; j++) {
frequencies[res[j]]++;
}
free(res);
}
for (i = 0; i < num_items; i++) {
printf(" %d", frequencies[i]);
}
puts("");
return 0;
}

View file

@ -0,0 +1,31 @@
s_of_n_creator = (n) ->
arr = []
cnt = 0
(elem) ->
cnt += 1
if cnt <= n
arr.push elem
else
pos = Math.floor(Math.random() * cnt)
if pos < n
arr[pos] = elem
arr.sort()
sample_size = 3
range = [0..9]
num_trials = 100000
counts = {}
for digit in range
counts[digit] = 0
for i in [1..num_trials]
s_of_n = s_of_n_creator(sample_size)
for digit in range
sample = s_of_n(digit)
for digit in sample
counts[digit] += 1
for digit in range
console.log digit, counts[digit]

View file

@ -0,0 +1,11 @@
> coffee knuth_sample.coffee
0 29899
1 29841
2 29930
3 30058
4 29932
5 29948
6 30047
7 30114
8 29976
9 30255

View file

@ -0,0 +1,26 @@
(defun s-n-creator (n)
(let ((sample (make-array n :initial-element nil))
(i 0))
(lambda (item)
(if (<= (incf i) n)
(setf (aref sample (1- i)) item)
(when (< (random i) n)
(setf (aref sample (random n)) item)))
sample)))
(defun algorithm-s ()
(let ((*random-state* (make-random-state t))
(frequency (make-array '(10) :initial-element 0)))
(loop repeat 100000
for s-of-n = (s-n-creator 3)
do (flet ((s-of-n (item)
(funcall s-of-n item)))
(map nil
(lambda (i)
(incf (aref frequency i)))
(loop for i from 0 below 9
do (s-of-n i)
finally (return (s-of-n 9))))))
frequency))
(princ (algorithm-s))

View file

@ -0,0 +1 @@
#(30026 30023 29754 30017 30267 29997 29932 29990 29965 30029)

View file

@ -0,0 +1,30 @@
import std.stdio, std.random;
auto sofN_creator(in int n) {
size_t i;
int[] sample;
return (in int item) {
i++;
if (i <= n)
sample ~= item;
else if (uniform(0.0, 1.0) < (cast(double)n / i))
sample[uniform(0, n)] = item;
return sample;
};
}
void main() {
enum nRuns = 100_000;
size_t[10] bin;
foreach (trial; 0 .. nRuns) {
auto sofn = sofN_creator(3);
int[] sample;
foreach (item; 0 .. bin.length)
sample = sofn(item);
foreach (s; sample)
bin[s]++;
}
writefln("Item counts for %d runs:\n%s", nRuns, bin);
}

View file

@ -0,0 +1,30 @@
import std.stdio, std.random, std.algorithm;
struct SOfN(int n) {
size_t i;
int[n] sample = void;
static rng = Xorshift(0);
int[] next(in int item) {
i++;
if (i <= n)
sample[i - 1] = item;
else if (uniform(0.0, 1.0, rng) < (cast(double)n / i))
sample[uniform(0, n, rng)] = item;
return sample[0 .. min(i, $)];
}
}
void main() {
enum nRuns = 100_000;
size_t[10] bin;
foreach (trial; 0 .. nRuns) {
SOfN!(3) sofn;
foreach (item; 0 .. bin.length - 1)
sofn.next(item);
foreach (s; sofn.next(bin.length - 1))
bin[s]++;
}
writefln("Item counts for %d runs:\n%s", nRuns, bin);
}

View file

@ -0,0 +1,38 @@
package main
import (
"fmt"
"math/rand"
"time"
)
func sOfNCreator(n int) func(byte) []byte {
s := make([]byte, 0, n)
m := n
return func(item byte) []byte {
if len(s) < n {
s = append(s, item)
} else {
m++
if rand.Intn(m) < n {
s[rand.Intn(n)] = item
}
}
return s
}
}
func main() {
rand.Seed(time.Now().UnixNano())
var freq [10]int
for r := 0; r < 1e5; r++ {
sOfN := sOfNCreator(3)
for d := byte('0'); d < '9'; d++ {
sOfN(d)
}
for _, d := range sOfN('9') {
freq[d-'0']++
}
}
fmt.Println(freq)
}

View file

@ -0,0 +1,24 @@
import Utils
procedure main(A)
freq := table(0)
every 1 to (\A[2] | 100000)\1 do {
s_of_n := s_of_n_creator(\A[1] | 3)
every sample := s_of_n(0 to 9)
every freq[!sample] +:= 1
}
every write(i := 0 to 9,": ",right(freq[i],6))
end
procedure s_of_n_creator(n)
items := []
itemCnt := 0.0
return makeProc {
repeat {
item := (items@&source)[1]
itemCnt +:= 1
if *items < n then put(items, item)
else if ?0 < (n/itemCnt) then ?items := item
}
}
end

View file

@ -0,0 +1,25 @@
coclass'inefficient'
create=:3 :0
N=: y
ITEMS=: ''
K=:0
)
s_of_n=:3 :0
K=: K+1
if. N>#ITEMS do.
ITEMS=: ITEMS,y
else.
if. (N%K)>?0 do.
ITEMS=: (((i.#ITEMS)-.?N){ITEMS),y
else.
ITEMS
end.
end.
)
s_of_n_creator_base_=: 1 :0
ctx=: conew&'inefficient' m
s_of_n__ctx
)

View file

@ -0,0 +1,19 @@
run=:3 :0
nl=. conl 1
s3_of_n=. 3 s_of_n_creator
r=. {: s3_of_n"0 i.10
coerase (conl 1)-.nl
r
)
(~.,._1 + #/.~) (i.10),,D=:run"0 i.1e5
0 30099
1 29973
2 29795
3 29995
4 29996
5 30289
6 29903
7 29993
8 30215
9 29742

View file

@ -0,0 +1,37 @@
import java.util.*;
class SOfN<T> {
private static final Random rand = new Random();
private List<T> sample;
private int i = 0;
private int n;
public SOfN(int _n) {
n = _n;
sample = new ArrayList<T>(n);
}
public List<T> process(T item) {
i++;
if (i <= n) {
sample.add(item);
} else if (rand.nextInt(i) < n) {
sample.set(rand.nextInt(n), item);
}
return sample;
}
}
public class AlgorithmS {
public static void main(String[] args) {
int[] bin = new int[10];
for (int trial = 0; trial < 100000; trial++) {
SOfN<Integer> s_of_n = new SOfN<Integer>(3);
List<Integer> sample = null;
for (int i = 0; i < 10; i++)
sample = s_of_n.process(i);
for (int s : sample)
bin[s]++;
}
System.out.println(Arrays.toString(bin));
}
}

View file

@ -0,0 +1,37 @@
import java.util.*;
interface Function<S, T> {
public T call(S x);
}
public class AlgorithmS {
private static final Random rand = new Random();
public static <T> Function<T, List<T>> s_of_n_creator(final int n) {
return new Function<T, List<T>>() {
private List<T> sample = new ArrayList<T>(n);
private int i = 0;
public List<T> call(T item) {
i++;
if (i <= n) {
sample.add(item);
} else if (rand.nextInt(i) < n) {
sample.set(rand.nextInt(n), item);
}
return sample;
}
};
}
public static void main(String[] args) {
int[] bin = new int[10];
for (int trial = 0; trial < 100000; trial++) {
Function<Integer, List<Integer>> s_of_n = s_of_n_creator(3);
List<Integer> sample = null;
for (int i = 0; i < 10; i++)
sample = s_of_n.call(i);
for (int s : sample)
bin[s]++;
}
System.out.println(Arrays.toString(bin));
}
}

View file

@ -0,0 +1,28 @@
<?php
function s_of_n_creator($n) {
$sample = array();
$i = 0;
return function($item) use (&$sample, &$i, $n) {
$i++;
if ($i <= $n) {
// Keep first n items
$sample[] = $item;
} else if (rand(0, $i-1) < $n) {
// Keep item
$sample[rand(0, $n-1)] = $item;
}
return $sample;
};
}
$items = range(0, 9);
for ($trial = 0; $trial < 100000; $trial++) {
$s_of_n = s_of_n_creator(3);
foreach ($items as $item)
$sample = $s_of_n($item);
foreach ($sample as $s)
$bin[$s]++;
}
print_r($bin);
?>

View file

@ -0,0 +1,34 @@
use strict;
sub s_of_n_creator {
my $n = shift;
my @sample;
my $i = 0;
sub {
my $item = shift;
$i++;
if ($i <= $n) {
# Keep first n items
push @sample, $item;
} elsif (rand() < $n / $i) {
# Keep item
@sample[rand $n] = $item;
}
@sample
}
}
my @items = (0..9);
my @bin;
foreach my $trial (1 .. 100000) {
my $s_of_n = s_of_n_creator(3);
my @sample;
foreach my $item (@items) {
@sample = $s_of_n->($item);
}
foreach my $s (@sample) {
$bin[$s]++;
}
}
print "@bin\n";

View file

@ -0,0 +1,13 @@
(de s_of_n_creator (@N)
(curry (@N (I . 0) (Res)) (Item)
(cond
((>= @N (inc 'I)) (push 'Res Item))
((>= @N (rand 1 I)) (set (nth Res (rand 1 @N)) Item)) )
Res ) )
(let Freq (need 10 0)
(do 100000
(let S_of_n (s_of_n_creator 3)
(for I (mapc S_of_n (0 1 2 3 4 5 6 7 8 9))
(inc (nth Freq (inc I))) ) ) )
Freq )

View file

@ -0,0 +1,34 @@
from random import randrange
def s_of_n_creator(n):
sample, i = [], 0
def s_of_n(item):
nonlocal i
i += 1
if i <= n:
# Keep first n items
sample.append(item)
elif randrange(i) < n:
# Keep item
sample[randrange(n)] = item
return sample
return s_of_n
if __name__ == '__main__':
bin = [0]* 10
items = range(10)
print("Single run samples for n = 3:")
s_of_n = s_of_n_creator(3)
for item in items:
sample = s_of_n(item)
print(" Item: %i -> sample: %s" % (item, sample))
#
for trial in range(100000):
s_of_n = s_of_n_creator(3)
for item in items:
sample = s_of_n(item)
for s in sample:
bin[s] += 1
print("\nTest item frequencies for 100000 runs:\n ",
'\n '.join("%i:%i" % x for x in enumerate(bin)))

View file

@ -0,0 +1,16 @@
class S_of_n_creator():
def __init__(self, n):
self.n = n
self.i = 0
self.sample = []
def __call__(self, item):
self.i += 1
n, i, sample = self.n, self.i, self.sample
if i <= n:
# Keep first n items
sample.append(item)
elif randrange(i) < n:
# Keep item
sample[randrange(n)] = item
return sample

View file

@ -0,0 +1 @@
s_of_n = S_of_n_creator(3)

View file

@ -0,0 +1,45 @@
/*REXX program using Knuth's algorithm S (random sampling n of M items).*/
parse arg trials size . /*obtain the arguments from C.L. */
if trials=='' then trials=100000 /*use default if not specified. */
if size=='' then size=3 /* " " " " " */
#.=0 /*a couple handfuls of counters. */
do trials /*OK, let's light this candle. */
call s_of_n_creator size /*create initial list of n items.*/
do gener=0 for 10 /*and then call SofN for each dig*/
call s_of_n gener /*call s_of_n with a single dig*/
end /*gener*/
do count=1 for size /*let's see what s_of_n wroth. */
_=!.count /*get a digit from the Nth item, */
#._=#._+1 /* ... and count it, of course. */
end /*count*/
end /*trials*/
say "Using Knuth's algorihm S for" comma(trials) 'trials, and with size='comma(size)":"
say
do dig=0 to 9 /*show & tell time for frequency.*/
say copies(' ',15) "frequency of the" dig 'digit is:' comma(#.dig)
end /*dig*/
exit /*stick a fork in it, we're done.*/
/*──────────────────────────────────S_OF_N_CREATOR subroutine───────────*/
s_of_n_creator: parse arg item 1 items /*generate ITEM number of items*/
do k=1 for item /*traipse through the 1st N items*/
!.k=random(0,9) /*set the Kth item with rand dig.*/
end /*k*/
return /*out piddly work is done for now*/
/*──────────────────────────────────S_OF_N subroutine───────────────────*/
s_of_n: parse arg item; items=items+1 /*get "item", bump items counter.*/
c=random(1,items) /*should we replace a prev item? */
if c>size then return /*probability isn't good, skip it*/
_=random(1,size) /*now, figure out which previous */
!._=item /* ... item to replace with ITEM.*/
return /*and back to the caller we go. */
/*──────────────────────────────────COMMA subroutine────────────────────*/
comma: procedure; parse arg _,c,p,t;arg ,cu;c=word(c ",",1)
if cu=='BLANK' then c=' '; o=word(p 3,1); p=abs(o); t=word(t 999999999,1)
if \datatype(p,'W') | \datatype(t,'W') | p==0 | arg()>4 then return _
n=_'.9'; #=123456789; k=0; if o<0 then do; b=verify(_,' '); if b==0 then return _
e=length(_)-verify(reverse(_),' ')+1; end; else do; b=verify(n,#,"M")
e=verify(n,#'0',,verify(n,#"0.",'M'))-p-1; end
do j=e to b by -p while k<t; _=insert(c,_,j); k=k+1; end; return _

View file

@ -0,0 +1,29 @@
#lang racket
(define (s-of-n-creator n)
(let* ([count 0] ; 'i' in the description
[vec (make-vector n)]) ; store the elts we've seen so far
(lambda (item)
(if (< count n)
; we're not full, so, kind of boring
(begin
(vector-set! vec count item)
(set! count (+ count 1))
(vector-copy vec 0 count))
; we've already seen n elts; fun starts
(begin
(set! count (+ count 1))
(when (< (random) (/ n count))
(vector-set! vec (random n) item))
(vector-copy vec))))))
(define counts (make-hash '((0 . 0) (1 . 0) (2 . 0) (3 . 0) (4 . 0) (5 . 0) (6 . 0) (7 . 0) (8 . 0) (9 . 0))))
(for ([iter (in-range 0 100000)]) ; trials
(let ([s-of-n (s-of-n-creator 3)]) ; set up the chooser
(for ([d (in-vector ; iterate over the chosen digits
(for/last ([digit (in-range 0 10)]) ; loop through the digits
(s-of-n digit)))]) ; feed them in
(hash-update! counts d add1)))) ; update counts
(for ([d (in-range 0 10)])
(printf "~a ~a~n" d (hash-ref counts d)))

View file

@ -0,0 +1,23 @@
def s_of_n_creator(n)
sample = []
i = 0
Proc.new do |item|
i += 1
if i <= n
sample << item
elsif rand(i) < n
sample[rand(n)] = item
end
sample
end
end
frequency = Array.new(10,0)
100_000.times do
s_of_n = s_of_n_creator(3)
sample = nil
(0..9).each {|digit| sample = s_of_n[digit]}
sample.each {|digit| frequency[digit] += 1}
end
(0..9).each {|digit| puts "#{digit}\t#{frequency[digit]}"}

View file

@ -0,0 +1,29 @@
package require Tcl 8.6
oo::class create SofN {
variable items size count
constructor {n} {
set size $n
}
method item {item} {
if {[incr count] <= $size} {
lappend items $item
} elseif {rand()*$count < $size} {
lset items [expr {int($size * rand())}] $item
}
return $items
}
}
# Test code
for {set i 0} {$i < 100000} {incr i} {
set sOf3 [SofN new 3]
foreach digit {0 1 2 3 4 5 6 7 8 9} {
set digs [$sOf3 item $digit]
}
$sOf3 destroy
foreach digit $digs {
incr freq($digit)
}
}
parray freq