【问题标题】:How can I randomly sample the contents of a file?如何随机采样文件的内容?
【发布时间】:2009-06-23 19:55:30
【问题描述】:

我有一个包含内容的文件

abc
def
high
lmn
...
...

文件中有超过 200 万行。 我想从文件中随机采样行并输出 50K 行。关于如何解决这个问题的任何想法?我在思考 Perl 及其 rand 函数的思路(或者一个方便的 shell 命令会很整洁)。

相关(可能重复)问题:

【问题讨论】:

  • 您要输出的行数是准确的还是算法可以输出大约 2.5% 的行数?

标签: perl file random sample


【解决方案1】:

假设您基本上想要输出大约 2.5% 的行,这样可以:

print if 0.025 > rand while <$input>;

【讨论】:

  • 如果文件大小不同,您可以通过计算行数(参见 perlfaq5)并将其除以所需的行数来计算百分比。
  • 这是一个非常好的解决方案,因为它避免了解决这个问题的简单方法,即跳转到文件中的随机点或(更糟!)对输入进行排序。
  • @James Thompson:虽然它看起来像是一个很好的解决方案,但它实际上并不是该问题的正确解决方案。无法保证它会返回 50k 行。
  • 如果您想对所有行的大约 2.5% 进行采样,我的解决方案是一个完美的解决方案。如果要求正好输出 50,000 行,我的不是正确的解决方案。我明确说明了这一点的大致性质。对于后一个问题,我相信我曾经读过一个单遍算法,但现在我不记得了。
  • 思南想到的算法叫做Reservoir Sampling Algorithm。本网站和 Internet 上的其他地方对此进行了很好的介绍。
【解决方案2】:

外壳方式:

sort -R file | head -n 50000

【讨论】:

  • 这是哪一种?我的(GNU coreutils 5.93)不支持-R。
  • [sinan@kas ~]$ sort --version sort (GNU coreutils) 7.4 版权所有 (C) 2009 Free Software Foundation, Inc.
  • => sort --version sort (GNU coreutils) 6.10
【解决方案3】:

来自perlfaq5: "How do I select a random line from a file?"


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

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

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

与读取整个文件相比,这在空间上具有显着优势。您可以在 计算机编程的艺术,第 2 卷,第 3.4.2 节中找到此方法的证明,作者 Donald E . 克努特。

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

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

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

【讨论】:

【解决方案4】:

Perl 方式:

使用 CPAN。有一个模块 File::RandomLine 可以满足您的需求。

【讨论】:

    【解决方案5】:

    如果您需要提取准确的行数:

    use strict;
    use warnings;
    
    # Number of lines to pick and file to pick from
    # Error checking omitted!
    my ($pick, $file) = @ARGV;
    
    open(my $fh, '<', $file)
        or die "Can't read file '$file' [$!]\n";
    
    # count lines in file
    my ($lines, $buffer);
    while (sysread $fh, $buffer, 4096) {
        $lines += ($buffer =~ tr/\n//);
    }
    
    # limit number of lines to pick to number of lines in file
    $pick = $lines if $pick > $lines;
    
    # build list of N lines to pick, use a hash to prevent picking the
    # same line multiple times
    my %picked;
    for (1 .. $pick) {
        my $n = int(rand($lines)) + 1;
        redo if $picked{$n}++
    }
    
    # loop over file extracting selected lines
    seek($fh, 0, 0);
    while (<$fh>) {
        print if $picked{$.};
    }
    close $fh;
    

    【讨论】:

    • 非常好的方法。唯一缺少的是检查 $pick
    • Bug:int(rand($lines)) 可以返回 0 但 $。从 1 开始。
    • @jermdemo: 啊,rand 返回的值小于参数,所以它不会选择最后一行。愚蠢的基于 1 的变量...我添加了一个 +1 来修复两种极端情况。
    猜你喜欢
    • 2018-06-11
    • 2016-10-16
    • 1970-01-01
    • 2021-08-20
    • 1970-01-01
    • 2020-06-04
    • 1970-01-01
    • 2021-10-12
    • 2018-11-17
    相关资源
    最近更新 更多