Statistics-Sampler-Multinomial
view release on metacpan or search on metacpan
lib/Statistics/Sampler/Multinomial/AliasMethod.pm view on Meta::CPAN
package Statistics::Sampler::Multinomial::AliasMethod;
use 5.014;
use warnings;
use strict;
our $VERSION = '1.02';
use Carp;
use Ref::Util qw /is_arrayref/;
use List::Util qw /min sum/;
use List::MoreUtils qw /first_index/;
use Scalar::Util qw /blessed/;
use parent qw /Statistics::Sampler::Multinomial/;
sub _validate_prng_object {
my ($self, $prng) = @_;
# Math::Random::MT::Auto has boolean op overloading
# so make sure we don't trigger it or our tests fail
# i.e. don't use "if $prng" directly
# (and we waste a random number, but that's less of an issue)
return 1 if !defined $prng;
croak 'prng arg is not an object'
if not blessed $prng;
croak 'prng arg does not have rand() method'
if not $prng->can('rand');
return 1;
}
sub _initialise_alias_tables {
my ($self, %args) = @_;
my $probs = $self->{data};
# caller has not promised they sum to 1
if (!$self->{data_sum_to_one}) {
my $sum = sum (@$probs);
if ($sum != 1) {
my @scaled_probs = map {$_ / $sum} @$probs;
$probs = \@scaled_probs;
}
}
# algorithm and comments stolen/adapted from
# https://hips.seas.harvard.edu/blog/2013/03/03/the-alias-method-efficient-sampling-with-many-discrete-outcomes/
my (@smaller, @larger);
my @J = (0) x scalar @$probs;
my @q = (0) x scalar @$probs;
my $kk = -1;
my $K = scalar @$probs;
foreach my $prob (@$probs){
$kk++;
$q[$kk] = $K * $prob;
if ($q[$kk] < 1.0) {
push @smaller, $kk
}
else {
push @larger, $kk;
}
}
# Loop though and create little binary mixtures that
# appropriately allocate the larger outcomes over the
# overall uniform mixture.
( run in 0.962 second using v1.01-cache-2.11-cpan-39bf76dae61 )