有一个类似于下一个名为"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;
这是一个有效的解决方案,但不是很好,因为:
- 每次运行脚本时,行顺序都会发生变化
- 并发脚本运行不安全
如何写得更有效、更优雅?
在不同的文件中保留一个打乱的行号列表,每次使用它时删除第一个,怎么样?可能需要一些锁定来衡量并发脚本运行的安全性。
来自perlfaq5。
如何从文件中随机选择一行?
缺少将文件加载到数据库或预索引中的行文件,有几件事你可以做。
以下是Camel Book中的储层采样算法:
srand; rand($.) < 1 && ($line = $_) while <>;
与读取整个文件相比,这在空间上具有显著优势你可以在《计算机艺术》中找到这种方法的证明编程,第2卷,第3.4.2节,作者:Donald E.Knuth。
您可以使用File::Random模块,该模块提供了算法:
use File::Random qw/random_line/; my $line = random_line($filename);
另一种方法是使用Tie::File模块,它处理整个文件作为数组。只需访问一个随机数组元素。
所有Perl程序员都应该花时间阅读常见问题解答。
更新:每次必须存储状态时,都要获得一个唯一的随机行。存储状态的最简单方法是从文件中删除已使用的行。
此程序使用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];