7

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

现在,除了使用链接问题的解决方案循环文件之外,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从数组中随机选择,但如果我有一个非常大的文件,那就是个问题。

有任何想法吗?

编辑:这是这个问题的完全重复。

4

7 回答 7

5

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

假设 M <= N。

  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所选行的集合。
于 2009-05-13T07:33:19.657 回答
2

这需要一个命令行参数,即您想要的行数 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;
于 2009-05-13T07:47:08.027 回答
1

可能的解决方案:

  1. 扫描一次以计算行数
  2. 决定随机选择的行号
  3. 再次扫描,挑线
于 2009-05-13T07:15:35.880 回答
1
@result = ();

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

print for @result;
于 2009-05-13T07:39:17.683 回答
1

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

这种方法对于大文件应该非常快,但它不适用于 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
   打印 $_;
}
于 2009-05-14T05:40:42.013 回答
0

在伪代码中:

use List::Util qw[shuffle];

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

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

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

于 2009-05-13T07:21:23.063 回答
0

下面是一些适用于大文件的冗长 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>;
}
于 2009-05-14T00:33:42.143 回答