【问题标题】:How can I get exactly n random lines from a file with Perl?如何使用 Perl 从文件中准确获取 n 个随机行?
【发布时间】:2010-10-25 18:48:47
【问题描述】:

跟进this 问题,我需要从文件(或stdin)中随机准确地获取n 行。这类似于headtail,除了我想要一些来自中间的。

现在,除了使用链接问题的解决方案循环文件之外,一次运行准确获得 n 行的最佳方法是什么?

作为参考,我试过这个:

#!/usr/bin/perl -w
use strict;
my $ratio = shift;
print $ratio, "\n";
while () {
    print if ((int rand $ratio) == 1); 
}

$ratio 是我想要的粗略百分比。例如,如果我想要 10 行中的 1 行:

random_select 10 a.list

但是,这并没有给我一个确切的数量:

aaa> foreach i ( 0 1 2 3 4 5 6 7 8 9 )
foreach? random_select 10 a.list | wc -l
foreach? end
4739
4865
4739
4889
4934
4809
4712
4842
4814
4817

我的另一个想法是吞食输入文件,然后从数组中随机选择n,但如果我有一个非常大的文件,那就是个问题了。

有什么想法吗?

编辑:这是this问题的完全相同。

【问题讨论】:

  • 这不是stackoverflow.com/questions/692312/…的完全相同的副本
  • 是的。对不起。我会将两者联系起来并投票关闭它。
  • 不,另一个问题允许样本被关闭 - 这个问题需要一个确切的数字。
  • 不要关闭它 - 它不是重复

标签: perl random-sample file-processing


【解决方案1】:

可能的解决方案:

  1. 扫描一次以计算行数
  2. 决定随机选择的行号
  3. 再次扫描,挑线

【讨论】:

  • 在标准输入上,扫描两次可能会有问题。
【解决方案2】:

在伪代码中:

use List::Util qw[shuffle];

# read and shuffle the whole file
@list = shuffle(<>);

# take the first 'n' from the list
splice(@list, ...);

这是最简单的实现,但您必须先读取整个文件,这需要您有足够的可用内存。

【讨论】:

  • 这正是我遇到的问题。我正在处理的文件是 63MB,而且需要很长时间。
  • 文件大小 63MB ?你有多少 MB 内存?我认为这个尺寸应该不是问题。
  • 确定行数,创建一个那么长的数组,打乱该数组,切掉您想要的行数,对该列表进行排序,然后遍历文件并输出行号该列表指定。应该和读取(整个)文件一样快。
【解决方案3】:

这是我刚刚提出的一个很好的一次性算法,具有 O(N) 时间复杂度和 O(M) 空间复杂度,用于从 N 行文件中读取 M 行。

假设 M

  1. S 成为选定行的集合。将S 初始化为文件的第一行M。如果最终结果的顺序很重要,请立即改组S
  2. 读入下一行l。到目前为止,我们已经阅读了n = M + 1 的总行数。因此,我们想要选择l 作为最后一行的概率是M/n
  3. 接受l,概率为M/n;使用 RNG 来决定是接受还是拒绝 l
  4. 如果l已被接受,则随机选择S中的一行替换为l
  5. 重复步骤 2-4,直到文件中的行用完为止,每读取一个新行,n 就会递增。
  6. 返回所选行的集合S

【讨论】:

  • 很好,但我认为你的意思是 M
  • 翻转符号是数学家永远的敌人。固定,叹了口气。
  • 另外,除非 N >> M ,否则不会对原始 M 行有偏见吗?
  • 据我所知没有;考虑从 6 行文件中选择 5 行。其中一条线将被排除;以 5/6 的概率,它将是前 5 个之一,以 1/6 的概率,它将是最后一个;这正是你想要的。该算法的棘手之处在于n,以及随之而来的拒绝概率,会随着读入更多行而发生变化。
  • 在基于流的文件系统(大多数现代的东西,包括 windows 和 unix)上,找到“行”是一个昂贵的提议。 (大量比较以找到行终止符)我下面的解决方案通过使用 seek 在文件中随机搜索然后向前搜索以捕获下一个完整行来解决这个问题。
【解决方案4】:
@result = ();

$k = 0;
while(<>) {
    $k++;
    if (scalar @result < $n) {
        push @result, $_;
    } else {
        if (rand <= $n/$k) {
            $result[int rand $n] = $_;
        }
    }
}

print for @result;

【讨论】:

  • 你的 rand 测试是错误的 - 它应该是 $n / $k,而不是 1.0 / $k;
【解决方案5】:

这需要一个命令行参数,即你想要的行数,N。 前 N 行被保留,因为您可能再也看不到了。此后,你随机 决定是否走下一行。如果你这样做,你随机决定哪条线 在当前的 N 列表中进行覆盖。

#!/usr/bin/perl
my $bufsize = shift;
my @list = ();

srand();
while (<>)
{
    push(@list, $_), next if (@list < $bufsize);
    $list[ rand(@list) ] = $_ if (rand($. / $bufsize) < 1);
}
print foreach @list;

【讨论】:

    【解决方案6】:

    这里有一些可以处理大文件的冗长 Perl 代码。

    这段代码的核心是它不会将整个文件存储在内存中,而只是将偏移量存储在文件中。

    使用tell 获取偏移量。然后seek到合适的地方恢复线路。

    更好的目标文件规范和要获取的行数留给那些比我懒的人练习。这些问题已经很好地解决了。

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use List::Util qw(shuffle);
    
    my $GET_LINES = 10; 
    
    my @line_starts;
    open( my $fh, '<', 'big_text_file' )
        or die "Oh, fudge: $!\n";
    
    do {
        push @line_starts, tell $fh
    } while ( <$fh> );
    
    my $count = @line_starts;
    print "Got $count lines\n";
    
    my @shuffled_starts = (shuffle @line_starts)[0..$GET_LINES-1];
    
    for my $start ( @shuffled_starts ) {
    
        seek $fh, $start, 0
            or die "Unable to seek to line - $!\n";
    
        print scalar <$fh>;
    }
    

    【讨论】:

      【解决方案7】:

      无需知道文件中的实际行号。只需寻找一个随机位置并保留 next 行。 (当前行很可能是部分行。)

      这种方法对于大文件应该非常快,但它不适用于 STDIN。哎呀,没有任何一种将整个文件缓存在内存中的方式适用于 STDIN。所以,如果你必须有 STDIN,我看不出你怎么能快速/便宜地处理大文件。

      您可以检测 STDIN 并切换到缓存方法,否则会很快。

      #!perl 使用严格; 我的 $file='file.txt'; 我的 $count=班次 || 10个; 我的 $size=-s $file; 打开(文件,$文件)|| die "无法打开 $file\n"; 而($count--){ 寻求(文件,int(rand($size)),0); $_=readline(文件); # 忽略部分行 重做,除非定义 ($_ = readline(FILE)); # 捕获 EOF 打印 $_; }

      【讨论】:

      • 请注意,这种方法不会从文件中统一选择行。一条线被选中的概率将由前一条线的长度加权;如果所有行的长度都相同,那没问题。但是,如果您需要从具有不同长度的行的文件中严格统一分布行,则需要一种不同的方法。
      • grrrr 你是对的......哦,好吧......它快 :) 但如果记录长度是静态的......或非常接近时很有用。
      猜你喜欢
      • 2012-02-16
      • 1970-01-01
      • 1970-01-01
      • 2021-08-20
      • 1970-01-01
      • 1970-01-01
      • 2015-07-02
      • 2022-01-06
      • 2011-02-18
      相关资源
      最近更新 更多