File Coverage

blib/lib/Math/Random/Discrete.pm
Criterion Covered Total %
statement 44 44 100.0
branch 9 12 75.0
condition 4 9 44.4
subroutine 5 5 100.0
pod 2 2 100.0
total 64 72 88.8


line stmt bran cond sub pod time code
1             package Math::Random::Discrete;
2             $Math::Random::Discrete::VERSION = '1.02';
3 2     2   79483 use strict;
  2         5  
  2         131  
4 2     2   17 use warnings;
  2         3  
  2         107  
5              
6 2     2   14 use Carp qw(croak);
  2         10  
  2         1325  
7              
8             # ABSTRACT: Discrete random variables with general distributions
9              
10             # This is an implementation of Walker's alias method.
11              
12             sub new {
13 1     1 1 64 my ($class, $_weights, $values) = @_;
14              
15 1 50 33     6 croak("No weights specified")
16             if !defined($_weights) || !@$_weights;
17 1 50 33     8 croak("Number of values must equal number of weights")
18             if defined($values) && @$values != @$_weights;
19              
20 1         3 my @weights = @$_weights;
21              
22             # compute average weight
23              
24 1         1 my $N = @weights;
25 1         2 my $sum = 0;
26              
27 1         2 for my $weight (@weights) {
28 10         11 $sum += $weight;
29             }
30              
31 1         3 my $avg = $sum / $N;
32              
33             # split weights into two groups: smaller and larger than average
34              
35 1         1 my (@small, @large);
36              
37 1         4 for (my $i = 0; $i < $N; ++$i) {
38 10 100       14 if ($weights[$i] <= $avg) {
39 5         8 push(@small, $i);
40             }
41             else {
42 5         8 push(@large, $i);
43             }
44             }
45              
46             # generate F and A arrays
47              
48 1         1 my (@F, @A);
49              
50 1   66     6 while (@small and @large) {
51 9         9 my $i = pop(@small);
52 9         7 my $j = $large[-1];
53 9         6 $A[$i] = $j;
54 9         8 $F[$i] = $weights[$i] / $avg;
55              
56 9         10 $weights[$j] -= $avg - $weights[$i];
57              
58 9 100       30 push(@small, pop(@large))
59             if $weights[$j] <= $avg;
60             }
61              
62 1         1 for my $i (@small, @large) {
63 1         2 $A[$i] = $i;
64 1         2 $F[$i] = 1.0;
65             }
66              
67             # create blessed ref
68              
69 1         5 my $self = {
70             values => $values,
71             A => \@A,
72             F => \@F,
73             };
74              
75 1         4 return bless($self, $class);
76             }
77              
78             sub rand {
79 55000     55000 1 265070 my $self = shift;
80              
81 55000         48687 my $F = $self->{F};
82 55000         50992 my $r = CORE::rand(@$F);
83 55000         47064 my $ri = int($r); # integer part
84 55000         43697 my $rf = $r - $ri; # fractional part
85              
86 55000 100       79908 my $i = $rf < $F->[$ri] ? $ri : $self->{A}[$ri];
87              
88 55000         45160 my $values = $self->{values};
89              
90 55000 50       102362 return $values ? $values->[$i] : $i;
91             }
92              
93             1;
94              
95             __END__