我必须在这里假设一些事情,并留下一些限制
首先构建一个随机数排序列表;这些是文件中区域开始的位置。然后,在读取每一行时,计算文件中的字符范围,并检查我们的数字是否在其中。如果有的话,它们会标记每个随机区域的开始:从这些字符开始选择所需长度的子字符串。检查子字符串是否适合该行。
use warnings;
use strict;
use feature 'say';
use Getopt::Long;
use List::MoreUtils qw(uniq);
my ($region_len, $num_regions) = (10, 10);
my $count_freq_for = 'F';
#srand(10);
GetOptions(
'num-regions|n=i' => \$num_regions,
'region-len|l=i' => \$region_len,
'char|c=s' => \$count_freq_for,
) or usage();
my $file = shift || usage();
# List of (up to) $num_regions random numbers, spanning the file size
# However, we skip all '>sp' lines so take more numbers (estimate)
open my $fh, '<', $file or die "Can't open $file: $!";
$num_regions += int $num_regions * fraction_skipped($fh);
my @rand = uniq sort { $a <=> $b }
map { int(rand (-s $file)-$region_len) } 1..$num_regions;
say "Starting positions for regions: @rand";
my ($nchars_prev, $nchars, $chars_left) = (0, 0, 0);
my $region;
while (my $line = <$fh>) {
chomp $line;
# Total number of characters so far, up to this line and with this line
$nchars_prev = $nchars;
$nchars += length $line;
next if $line =~ /^\s*>sp/;
# Complete the region if there wasn't enough chars on the previous line
if ($chars_left > 0) {
$region .= substr $line, 0, $chars_left;
my $cnt = () = $region =~ /$count_freq_for/g;
say "$region $cnt";
$chars_left = -1;
};
# Random positions that happen to be on this line
my @pos = grep { $_ > $nchars_prev and $_ < $nchars } @rand;
# say "\tPositions on ($nchars_prev -- $nchars) line: @pos" if @pos;
for (@pos) {
my $pos_in_line = $_ - $nchars_prev;
$region = substr $line, $pos_in_line, $region_len;
# Don't print if there aren't enough chars left on this line
last if ( $chars_left =
($region_len - (length($line) - $pos_in_line)) ) > 0;
my $cnt = () = $region =~ /$count_freq_for/g;
say "$region $cnt";
}
}
sub fraction_skipped {
my ($fh) = @_;
my ($skip_len, $data_len);
my $curr_pos = tell $fh;
seek $fh, 0, 0 if $curr_pos != 0;
while (<$fh>) {
chomp;
if (/^\s*>sp/) { $skip_len += length }
else { $data_len += length }
}
seek $fh, $curr_pos, 0; # leave it as we found it
return $skip_len / ($skip_len+$data_len);
}
sub usage {
say STDERR "Usage: $0 [options] file", "\n\toptions: ...";
exit;
}
取消注释srand
行以便始终具有相同的运行,以进行测试。
注释如下。
一些极端情况
-
如果 10 长的窗口不适合从其随机位置开始的行,它将在下一行中完成 - 但任何(可能的)further该线上的随机位置被省略。因此,如果我们的随机列表有 1120 和 1122,而一行以 1125 结束,则跳过从 1122 开始的窗口。不太可能,有可能,并且没有任何影响(除了少一个区域之外)。
-
当下一行(第一行)填充不完整区域时if
in the while
循环),它是possible该行比剩余的所需字符短($chars_left
)。这种情况不太可能发生,需要进行额外的检查,但被遗漏了。
-
随机数被删除了。这扭曲了顺序,但这里不重要的是什么;我们可能会留下比要求的人数少的人,但也只是少一点
处理随机性问题
这里的“随机性”是非常基本的,看起来合适的。我们还需要考虑以下因素。
在文件大小的区间内绘制随机数,int(rand -s $file)
(减去区域大小)。但线>sp
被跳过,并且任何可能落在这些行内的数字都不会被使用,因此我们最终得到的区域可能比抽出的数字少。这些行较短,因此上面有数字的机会较小,因此不会丢失太多数字,但在某些运行中,我什至看到 10 个数字中有 3 个被跳过,最终得到了所需大小的 70% 的随机样本。
如果这很麻烦,有一些方法可以解决它。为了不进一步扭曲分布,它们都应该涉及对文件的预处理。
上面的代码对文件进行初始运行,以计算将跳过的字符比例。然后将其用于增加抽取的随机点的数量。这当然是一个“平均”测量,但仍应生成接近足够大文件所需的区域数量。
更详细的措施需要查看(更大)分布的哪些随机点将因跳行而丢失,然后重新采样以解决这一问题。这可能仍然会扰乱分发,这可以说不是问题,但更重要的是可能根本不需要。
在这一切中,您读取了大文件两次。额外的处理时间应该仅以秒为单位,但如果这是不可接受的,请更改函数fraction_skipped
仅读取文件的 10-20%。对于大文件,这仍然应该提供合理的估计。
关于特定测试用例的注释
With srand(10)
(靠近开头的注释行)我们得到随机数,使得在一行上该区域在该行末尾之前的 8 个字符处开始!因此,该案例确实测试了代码以完成下一行的区域。
这是一个简单的驱动程序,用于运行上述指定次数,以进行统计。
Doing it using builtin tools (system
, qx
) is altogether harder and libraries (modules) help. I use IPC::Run https://metacpan.org/pod/IPC::Run here. There are quite a few other options.†
根据统计需要调整并添加处理代码;输出在文件中。
use warnings;
use strict;
use feature 'say';
use Getopt::Long;
use IPC::Run qw(run);
my $outdir = 'rr_output'; # pick a directory name
mkdir $outdir if not -d $outdir;
my $prog = 'random_regions.pl'; # your name for the program
my $input = 'data_file.txt'; # your name for input file
my $ch = 'F';
my ($runs, $regions, $len) = (10, 10, 10);
GetOptions(
'runs|n=i' => \$runs,
'regions=i' => \$regions,
'length=i' => \$len,
'char=s' => \$ch,
'input=s' => \$input
) or usage();
my @cmd = ( $prog, $input,
'--num-regions', $regions,
'--region-len', $len,
'--char', $ch
);
say "Run: @cmd, $runs times.";
for my $n (1..$runs) {
my $outfile = "$outdir/regions_r$n.txt";
say "Run #$n, output in: $outdir/$outfile";
run \@cmd, '>', $outfile or die "Error with @cmd: $!";
}
sub usage {
say STDERR "Usage: $0 [options]", "\n\toptions: ...";
exit;
}
请扩展错误检查。参见例如这个帖子 https://stackoverflow.com/a/40777096/4653379以及详细信息的链接。
最简单的使用:driver_random.pl -n 4
,但您可以给出所有主程序的参数。
被调用的程序(random_regions.pl
上面)必须是可执行的。
† Some, from simple to more capable: IPC::System::Simple https://metacpan.org/pod/IPC::System::Simple, Capture::Tiny https://metacpan.org/pod/Capture::Tiny, IPC::Run3 https://metacpan.org/pod/IPC::Run3. (Then comes IPC::Run
used here.) Also see String::ShellQuote https://metacpan.org/pod/String::ShellQuote, to prepare commands without quoting issues, shell injection bugs, and other problems. See links (examples) assembled in this post https://stackoverflow.com/a/44185799/4653379, for example.