采样间隔,而不是数字,无需更换

时间:2014-06-13 02:07:25

标签: arrays perl random sampling resampling

我正在处理的问题涉及一些问题,即:

  1. 我需要从一系列数字中随机抽取数字。
  2. 这个数字范围非常大,从1到1,000,000,000。
  3. 我需要采样过程以避免从已经采样的范围内的采样中采样。由于使用数组太慢,我尝试使用splice无法正常工作。
  4. 我首先选择1到1,000,000,000之间的数字。

    my $random = int(rand(1_000_000_000)) + 1;
    

    我添加一个值,比如说100,以使$random$random + 100定义一个间隔。

    my $interval = $random + 100;
    

    然后我push$random$interval放入另一个数组中。另一个数组是存储间隔。

    push ( @rememberOldIntervals, $random, $interval );
    

    我使用@rememberOldIntervals循环遍历数组for,成对提取项目。一对中的第一个是前$random,另一个是$interval。在这个for循环中,我做了另一个随机数生成。但是生成的数字不能在已经采用的间隔之间。如果是这样,请继续采样,直到找到唯一的数字。此外,这个新的随机数必须与任何旧的间隔至少相距100。

    for ( my $i= 0; $i < (scalar @rememberOldIntervals) / 2 ; $i=+2) {
          $random = int(rand(1_000_000_000)) + 1;
          my $new_random_low  = $random - 100;
          my $new_random_high = $random + 100;
    
          if ( $new_random_low  <= $rememberOldIntervals[0] OR 
               $new_random_high >= $rememberOldIntervals[1]    ){
    
              push( @rememberOldIntervals, $new_random_low, $new_random_high ); 
          }
    
          else {
                until ($new_random_low  <= $rememberOldIntervals[0] OR 
                       $new_random_high >= $rememberOldIntervals[1]    ) {
    
                       $random = int(rand(1_000_000_000)) + 1;
                       my $new_random_low  = $random - 100;
                       my $new_random_high = $random + 100;
                }
          }
    
    }
    

    后一个循环需要嵌入另一个循环中以驱动它多次,比如10,000次。

2 个答案:

答案 0 :(得分:1)

您可以使用哈希值和索引加快速度。

这会将空间分成宽度为200的索引段,每个间隔将随机放置在一个随机段中。

my $interval = 100;
my $space = 1e9;
my $interval_count = 1e4;
my @values;
my %index_taken;
for(1..$interval_count)
{
    my $index;
    $index while $index_taken{$index = int rand $space/2/$interval }++;
    my $start = $index*2*$interval + 1 + int rand $interval;
    push @values, $start, $start+$interval;
}

它保证不重叠的间隔,但在两个间隔之间将存在高达200的无法访问的空间。

或者,如果您希望间隔排序:

@values = map {$_*=2*$interval; $_+=1+int rand $interval; ($_,$_+$interval)} 
    sort keys %index_taken;

答案 1 :(得分:1)

这个问题可以重新定义为在0到10亿之间抽取10,000个随机数,其中没有数字在100之内。

蛮力 - 5秒

因为你只提取10,000个数字,而且可能不需要经常这样做,我建议最初使用暴力来解决这类问题。这是试图遵循Premature optimization is the root of all evil

的设计模式

在这种情况下,这意味着只需提取随机数并将它们与之前提取的所有数字进行比较。这将具有O(N^2)的速度,但也将减少代码。

use strict;
use warnings;

my $max = 1_000_000_000;
my $dist = 100;
my $count = 10_000;

die "Too many numbers" if 2 * $dist * $count >= $max;

my @numbers;

while (@numbers < $count) {
    my $num = int rand $max;
    push @numbers, $num if ! grep {abs($num - $_) < $dist} @numbers;
}

print scalar(@numbers), "\n";

输出需要5秒钟:

10000

二进制搜索更快生成 - 0.14秒

现在为了更快的算法,我同意ysth一个更有效的解决方法是创建两个随机数列表。其中一个是运行列表,另一个是排序。使用排序列表对位置进行二进制搜索,然后与附近的元素进行比较,看它是否在100之内。

这会减少从O(N^2)O(N log N)的比较次数。运行时间仅为0.14秒,而暴力方法则为5秒。

use strict;
use warnings;

my $max = 1_000_000_000;
my $dist = 100;
my $count = 10_000;

die "Too many numbers" if 2 * $dist * $count >= $max;

my @numbers;
my @sorted = (-$dist, $max);   # Include edges to simplify binary search logic.

while (@numbers < $count) {
    my $num = int rand $max;

    # Binary Search of Sorted list.
    my $binary_min = 0;
    my $binary_max = $#sorted;
    while ($binary_max > $binary_min) {
        my $average = int( ($binary_max + $binary_min) / 2 );
        $binary_max = $average if $sorted[$average] >= $num;
        $binary_min = $average + 1 if $sorted[$average] <= $num;
    }

    if (! grep {abs($num - $_) < $dist} @sorted[$binary_max, $binary_max - 1]) {
        splice @sorted, $binary_max, 0, $num;
        push @numbers, $num;
    }
}

print scalar(@numbers), "\n";

最快的商数哈希 - 0.05秒

我在评论中询问:“你可以简化这个问题来选择100的随机倍数吗?这样可以确保没有重叠,然后你只需要选择1到1千万的随机数而不需要重复,然后将它乘以100。“你没有回应,但我们仍然可以使用100的倍数进行分组来简化这个问题。

基本上,如果我们跟踪一个数字除以100的商数,我们只需要将它与商数加减1的数字进行比较。这减少了与O(N)的比较次数,这在0.05秒内最快就是最快:

use strict;
use warnings;

my $max = 1_000_000_000;
my $dist = 100;
my $count = 10_000;

die "Too many numbers" if 2 * $dist * $count >= $max;

my @numbers;
my %num_per_quot;

while (@numbers < $count) {
    my $num = int rand $max;

    my $quotient = int $num / $dist;

    if (! grep {defined && abs($num - $_) < $dist} map {$num_per_quot{$quotient + $_}} (-1, 0, 1)) {
        push @numbers, $num;
        $num_per_quot{$quotient} = $num;
    }
}

print scalar(@numbers), "\n";

如果您使用的是

,请注意

如果您在Windows上运行此代码并且使用的perl版本低于v5.20,则需要使用比内置rand更好的随机数生成。出于原因,请阅读avoid using rand if it matters

我在这段代码中使用了Math::Random::MT qw(rand);,因为我正在使用Strawberry Perl v5.18.2。但是,从Perl v5.20开始,这将不再是一个问题,因为rand now uses a consistent random number generator