Я должен предположить кое-что здесь и оставить некоторые ограничения
Выбор случайных областей не зависит от линий;они просто выбраны из текста
Порядок не имеет значения;в файле должно быть только N областей
Размер файла может достигать гигабайта, поэтому сначала его невозможно прочитать целиком (было бы намного проще!)
Есть необработанные (крайние или маловероятные) случаи, обсуждаемые после кода
Сначала создайте отсортированный список случайных чисел;это позиции в файле, с которых начинаются регионы.Затем, когда каждая строка прочитана, вычислите ее диапазон символов в файле и проверьте, попадают ли в нее наши числа.Если некоторые из них, они отмечают начало каждой случайной области: выбрать подстроки желаемой длины, начиная с этих символов.Проверьте, подходят ли подстроки на линии.
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 не умещается на линии из случайного положения, оно завершаетсяв следующей строке - но любые (возможные) дальнейшие случайные позиции в этой строке опущены.Таким образом, если в нашем случайном списке 1120 и 1122, а строка заканчивается в 1125, то окно, начинающееся в 1122, пропускается.Маловероятно, возможно и без последствий (кроме наличия на одну область меньше).
Когда в следующей строке заполняется неполная область (первая if
в while
loop), возможно , что эта строка короче, чем остальные необходимые символы ($chars_left
).Это очень маловероятно и нуждается в дополнительной проверке, которая здесь не указана.
Случайные числа обрезаются на дубли.Это искажает последовательность, но в мельчайших подробностях то, что здесь не имеет значения;и мы можем остаться с меньшим количеством номеров, чем запрошено, но только очень маленьким
Обработка вопросов, касающихся случайности
«Случайность» здесьдовольно простой, что кажется подходящим.Нам также необходимо учесть следующее:
Случайные числа рисуются в интервале, охватывающем размер файла, int(rand -s $file)
(минус размер региона).Но линии >sp
пропускаются, и любые из наших чисел, которые могут попадать в эти строки, не будут использоваться, и поэтому у нас может оказаться меньше регионов, чем нарисованных чисел.Эти строки короче, следовательно, с меньшей вероятностью иметь числа на них, и поэтому не многие числа теряются, но в некоторых прогонах я пропускал даже 3 из 10 пропущенных чисел, в результате чего случайная выборка имела размер 70% желаемого размера.
Если это беспокоит, есть способы подойти к нему.Чтобы не искажать дистрибутив еще дальше, все они должны включать предварительную обработку файла.
Приведенный выше код выполняет начальный прогон файла, чтобы вычислить долю символов, которые будут пропущены.Это затем используется для увеличения числа случайных точек.Это, конечно, «средняя» мера, но она все равно должна давать количество областей, близких к желаемым, для достаточно больших файлов.
Необходимы более подробные измерения, чтобы увидеть, какие случайные точки (гораздо большего) распределения будут потеряны для пропущенных линий, а затем повторно выбрать, чтобы учесть это.Это может все еще мешать распространению, что, возможно, не является проблемой здесь, но более конкретно, может просто не понадобиться.
Во всем этом вы читаете большой файл дважды.Дополнительное время обработки должно быть только в секундах, но если это недопустимо, измените функцию fraction_skipped
, чтобы прочитать только 10-20% файла.Для больших файлов это все равно должно дать разумную оценку.
Примечание по конкретному контрольному примеру
С помощью srand(10)
(закомментированная строка в начале) мы получаем случайные числа, так что в одной строке область начинается за 8 символов до конца строки!Таким образом, в этом случае проверяется код для завершения региона на следующей строке.
Простой драйвер для запуска вышеуказанного заданное количество раз для статистики.
Выполнение этого с использованием только встроенных инструментов (system
, qx
) затруднительно и требовательно;реально нужны модули.Поэтому я использую IPC :: Run здесь.Есть довольно много других вариантов. †
Настройка и добавление кода для обработки по мере необходимости для статистики;вывод находится в файлах.
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;
}
Пожалуйста, подробнее о проверке ошибок.Смотрите, например, этот пост и ссылки на детали.
Простейшее использование: driver_random.pl -n 4
, но вы можете указать все основные параметры программы.
Примечание: Вызываемая программа (random_regions.pl
выше) должна быть исполняемой.
† Некоторые, от простых до более способных: IPC :: System :: Simple, Capture :: Tiny , IPC :: Run3 .(Затем прибывает IPC::Run
, используемый здесь.) Также смотрите String :: ShellQuote , чтобы подготовить команды без проблем с кавычками, ошибок внедрения оболочки и других проблем.Смотрите ссылки (примеры), собранные в этом посте , например.