Получение уникальной случайной строки (при каждом запуске скрипта) из текстового файла с помощью perl
Текстовый файл, подобный следующему, называется "input.txt".
some field1a | field1b | field1c
...another approx 1000 lines....
fielaNa | field Nb | field Nc
Я могу выбрать любой разделитель полей.
Нужен скрипт, который при каждом дискретном запуске будет получать одну уникальную (никогда не повторяющуюся) случайную строку из этого файла, пока не будут использованы все строки.
Мое решение: я добавил один столбец в файл, поэтому
0|some field1a | field1b | field1c
...another approx 1000 lines....
0|fielaNa | field Nb | field Nc
и обрабатывает его следующим кодом:
use 5.014;
use warnings;
use utf8;
use List::Util;
use open qw(:std :utf8);
my $file = "./input.txt";
#read all lines into array and shuffle them
open(my $fh, "<:utf8", $file);
my @lines = List::Util::shuffle map { chomp $_; $_ } <$fh>;
close $fh;
#search for the 1st line what has 0 at the start
#change the 0 to 1
#and rewrite the whole file
my $random_line;
for(my $i=0; $i<=$#lines; $i++) {
if( $lines[$i] =~ /^0/ ) {
$random_line = $lines[$i];
$lines[$i] =~ s/^0/1/;
open($fh, ">:utf8", $file);
print $fh join("\n", @lines);
close $fh;
last;
}
}
$random_line = "1|NO|more|lines" unless( $random_line =~ /\w/ );
do_something_with_the_fields(split /\|/, $random_line))
exit;
Это рабочее решение, но не очень хорошее, потому что:
- порядок строк меняется при каждом запуске скрипта
- не одновременно безопасное выполнение сценария.
Как написать это эффектнее и элегантнее?
3 ответа
Эта программа использует Tie::File
модуль, чтобы открыть ваш input.txt
файл, а также indices.txt
файл.
Если indices.txt
пусто, то инициализируется индексами всех записей в input.txt
в случайном порядке.
При каждом запуске индекс в конце списка удаляется и отображается соответствующая входная запись.
use strict;
use warnings;
use Tie::File;
use List::Util 'shuffle';
tie my @input, 'Tie::File', 'input.txt'
or die qq(Unable to open "input.txt": $!);
tie my @indices, 'Tie::File', 'indices.txt'
or die qq(Unable to open "indices.txt": $!);
@indices = shuffle(0..$#input) unless @indices;
my $index = pop @indices;
print $input[$index];
Обновить
Я изменил это решение так, чтобы оно заполняло новый indices.txt
файл, только если он еще не существует, а не, как раньше, просто когда он пуст. Это означает, что новую последовательность записей можно распечатать, просто удалив indices.txt
файл.
use strict;
use warnings;
use Tie::File;
use List::Util 'shuffle';
my ($input_file, $indices_file) = qw( input.txt indices.txt );
tie my @input, 'Tie::File', $input_file
or die qq(Unable to open "$input_file": $!);
my $first_run = not -f $indices_file;
tie my @indices, 'Tie::File', $indices_file
or die qq(Unable to open "$indices_file": $!);
@indices = shuffle(0..$#input) if $first_run;
@indices or die "All records have been displayed";
my $index = pop @indices;
print $input[$index];
Как насчет того, чтобы хранить перемешанный список номеров строк в другом файле, удаляя первый при каждом его использовании? Для обеспечения безопасности при выполнении сценариев может потребоваться некоторая блокировка.
Из perlfaq5.
Как выбрать случайную строку из файла?
За исключением загрузки файла в базу данных или предварительной индексации строк в файле, есть несколько вещей, которые вы можете сделать.
Вот алгоритм отбора проб из книги верблюдов:
srand; rand($.) < 1 && ($line = $_) while <>;
Это имеет существенное преимущество в пространстве по сравнению с чтением всего файла. Доказательство этого метода можно найти в книге "Искусство компьютерного программирования", том 2, раздел 3.4.2, Дональда Кнута.
Вы можете использовать модуль File::Random, который предоставляет функцию для этого алгоритма:
use File::Random qw/random_line/; my $line = random_line($filename);
Другой способ - использовать модуль Tie::File, который обрабатывает весь файл как массив. Просто получите доступ к элементу случайного массива.
Все программисты Perl должны найти время, чтобы прочитать FAQ.
Обновление: чтобы получить уникальную случайную строку каждый раз, когда вам нужно будет сохранить состояние. Самый простой способ сохранить состояние - это удалить строки, которые вы использовали из файла.