in reply to How do I get a random index with weighting?

I developed a formula to randomly pick N items based on their weights (for what you ask for, N == 1) that I could use on an array and even in SQL, that you can find here. The basic idea is to sort the items based on a calculated random value from the expression
-log(1 - rand)/$weight
where $weight is the weight factor for the current item. You can find the rationale behind the formula in that post.

For one item, you can reduce the code to picking the item where this expression has the lowest value. There's no need to actually sort them all.

Here is code for the reduced problem:

my @matrix = ( [a => 0.1], [b => 0.5], [c => 0.4] ); my $chosen = pick(@matrix); sub pick { my($choice, $threshold); foreach(@_) { my $rand = -log(1 - rand)/$_->[1]; if(not defined $threshold or $rand < $threshold) { $choice = $_->[0]; $threshold = $rand; } } return $choice; }
For better randomness of a huge number of draws, it's better to pick a random number generator that has a higher number of bits (and a larger cycle) than the default one (only 15 bits on ActivePerl/Windows!), for example Math::Random::MT which can be used as a plug in replacement if you import (and override) rand.
use Math::Random::MT qw(rand);

To demonstrate that it works as advertised, here are the results of a test run of 100_000 draws:

my %picked; for(1 .. 1E5) { $picked{pick(@matrix)}++; } use Data::Dumper; print Dumper \%picked;
Output:
$VAR1 = { 'c' => 40200, 'a' => 10042, 'b' => 49758 };
which looks very close to the desired distribution, to me.

p.s. If there can be any items with weight 0, thus, that should never be picked, you will have to skip them at the top of the loop, because otherwise, the expression will barf (division by zero).