使用Perl从文本文件中获取每次运行时的唯一随机行

11

假设有一个名为"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个回答

8

你可以考虑在另一个文件中保留一组随机排列的行号,每次使用时删除第一个行号。为确保并发脚本运行的安全性,可能需要进行一些锁定操作。


我原本想建议只需添加另一列带有行号,以便可以按顺序重写它们,但这种方法更好,因为您根本不需要修改数据文件。 - primehunter326
谢谢你的想法!也许这会是解决方案。说实话,我希望有一些 CPAN 模块的想法,但当没有人提出更简单的解决方案时,我会遵循你的建议。谢谢。 :) - novacik

4

来自 Perl常见问题解答

How do I select a random line from a file?

Short of loading the file into a database or pre-indexing the lines in the file, there are a couple of things that you can do.

Here's a reservoir-sampling algorithm from the Camel Book:

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

This has a significant advantage in space over reading the whole file in. You can find a proof of this method in The Art of Computer Programming, Volume 2, Section 3.4.2, by Donald E. Knuth.

You can use the File::Random module which provides a function for that algorithm:

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

Another way is to use the Tie::File module, which treats the entire file as an array. Simply access a random array element.

所有的Perl程序员都应该花时间阅读FAQ。
更新:为了每次得到一个唯一的随机行,你需要存储状态。最简单的存储方法是从文件中删除已使用的行。

随机行应该是唯一的(需要一个脚本,每次离散运行时从这个文件中获取一条唯一(永不重复)的随机行,直到使用了所有行) - gangabass
3
所有Perl程序员都应该花时间阅读整个问题 :-) - choroba
我知道如何从文件中获取随机行。但在这种情况下,我需要在每次运行时获取__唯一__的随机行。引用FAQ很容易 - 但在这种情况下,这不是一个解决方案。无论如何,感谢您的回复。 - novacik
1
@Dave Cross:谢谢你提供的Tie::File(Perl语言模块)建议。看上去很不错。 - novacik

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];

谢谢您。这是我新解决方案的一部分。 :) 我正在使用 $index = splice(@indices, rand @indices, 1) ... accept。 - novacik
1
@novacik:那是无意义的。索引已经是随机顺序了,随机选择而不是线性选择不会使它们更“随机”。我选择使用 pop 是因为它会从文件末尾删除,所以速度最快。 - Borodin
1
Mean - 我在使用splice/rand而不是shuffle。 - novacik
@novacik:我已经更新了我的解决方案,以便在开始新的序列之前必须删除indices.txt文件。 - Borodin
好的,现在它像魔法一样正常工作。只洗牌一次,只弹出最后一行来缩短文件。太棒了! :) 谢谢。 - novacik
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接