在将少量莎士比亚书籍送到我的Perl脚本后,我有一个哈希,其中包含26个英文字母作为键,以及它们在文本中出现的次数 – 作为值:
%freq = ( a => 24645246,b => 1409459,.... z => 807451,);
当然还有所有字母的总数 – 让我们说在$total变量中.
是否有一个很好的技巧来生成一个包含16个随机字母的字符串(一个字母可以在那里出现几次) – 按使用频率加权?
要在类似于Ruzzle的文字游戏中使用:
优雅的东西 – 比如从文件中挑选一条随机行,如Perl Cookbook收据所示:
rand($.) < 1 && ($line = $_) while <>;
解决方法
选择随机线的Perl Cookbook技巧(也可以在
perlfaq5中找到)也可以用于加权采样:
my $chosen; my $sum = 0; foreach my $item (keys %freq) { $sum += $freq{$item}; $chosen = $item if rand($sum) < $freq{$item}; }
这里,$sum对应于行计数器$.和$freq {$item}到Cookbook版本中的常量1.
如果您要选择大量加权随机样本,可以通过一些准备加快这一点(注意这会破坏%freq,所以如果你想保留它,请先复制一份):
# first,scale all frequencies so that the average frequency is 1: my $avg = 0; $avg += $_ for values %freq; $avg /= keys %freq; $_ /= $avg for values %freq; # now,prepare the array we'll need for fast weighted sampling: my @lookup; while (keys %freq) { my ($lo,$hi) = (sort {$freq{$a} <=> $freq{$b}} keys %freq)[0,-1]; push @lookup,[$lo,$hi,$freq{$lo} + @lookup]; $freq{$hi} -= (1 - $freq{$lo}); delete $freq{$lo}; }
现在,要从准备好的分布中绘制随机加权样本,您只需执行以下操作:
my $r = rand @lookup; my ($lo,$threshold) = @{$lookup[$r]}; my $chosen = ($r < $threshold ? $lo : $hi);
(这基本上是Marsaglia,Tsang& Wang(2004),“Fast Generation of Discrete Random Variables”,J.Stat.Soft.11(3)中描述的Square Histogram方法,最初归于A.J.Walker(1974).)