12

有一个类似于下一个名为“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;

这是一个可行的解决方案,但不是很好,因为:

  • 每次脚本运行时,行顺序都会发生变化
  • 不是并发脚本运行安全的。

如何写得更有效更优雅?

4

3 回答 3

8

如果在不同的文件中保留行号的打乱列表,每次使用时删除第一个,怎么样?可能需要一些锁定来确保并发脚本运行的安全性。

于 2012-07-23T12:24:10.837 回答
4

perlfaq5

如何从文件中选择随机行?

除了将文件加载到数据库或预先索引文件中的行之外,您还可以做几件事。

这是来自骆驼书的水库采样算法:

srand;
rand($.) < 1 && ($line = $_) while <>;

与读取整个文件相比,这在空间上具有显着优势。您可以在 Donald E. Knuth 的 The Art of Computer Programming, Volume 2, Section 3.4.2 中找到这种方法的证明。

您可以使用为该算法提供函数的 File::Random 模块:

use File::Random qw/random_line/;
my $line = random_line($filename);

另一种方法是使用 Tie::File 模块,它将整个文件视为一个数组。只需访问一个随机数组元素。

所有 Perl 程序员都应该花时间阅读 FAQ。

更新:每次您必须存储状态时,都要获得一条唯一的随机线。存储状态的最简单方法是从文件中删除您使用过的行。

于 2012-07-23T12:47:17.597 回答
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];
于 2012-07-23T14:47:34.723 回答