比较单个文件中的行

时间:2014-02-25 19:04:12

标签: perl

我想知道是否有更有效的方式来做我正在尝试的事情。我需要读取文件并将文件的每一行与其后的所有文件进行比较。 (即将第1行与第2,3,4,5行比较......;第2行与3,4,5,...;第3行与4,5 ......等)。我觉得有些文件太大而无法完全读入@lists,所以我现在通过打开同一个文件的两个文件句柄并使用tell和seek来获取一个文件的位置并设置其他内容的位置来实现这一点像这样:

open FH1, '/arf/test.txt';
open FH2, '/arf/test.txt';

while ($outer = <FH1>){
    chomp($outer);
    $fh1Posn = tell FH1;
    seek FH2, $fh1Posn, 0;
    while ($inner = <FH2>){
        [code to compare line];
    }
}

这在小文件中工作得非常好,但是我需要处理一些较大的文件(即使我将伪代码替换为比较部分)。也许这是可以预料的,但我想知道是否有什么我可以尝试加速文件的阅读速度?我只想使用单个文件句柄,消除第二个并使用while循环底部的$ fh1Posn将光标重置回顶部的位置,如:

open FH1, '/arf/test.txt';

while ($outer = <FH1>){
    chomp($outer);
    $fh1Posn = tell FH1;
    while ($inner = <FH1>){
        [code to compare line];
    }
    seek FH1, $fh1Posn, 0;
}

Haven还试过这个 - 会这样做 - 但觉得它可能不会做任何有价值的事情。

1 个答案:

答案 0 :(得分:1)

最快的解决方案是完全在内存中完成,将磁盘读取限制为一次通过。如果你没有足够的内存来做到这一点,那么我建议将它分成块。

你当前的解决方案是读取文件O(n ^ 2)次是最糟糕的解决方案,内存不足。如果以1000行为一组进行,则可以大大减少磁盘操作。你必须尝试找到你实际内存限制的位置。

my $buffersize = 1000;

open FH1, '/arf/test.txt';
open FH2, '/arf/test.txt';

while (! eof(FH1)) {
    my @buffer = ();
    for (1..$buffersize) {
        my $outer = <FH1>;
        chomp $outer;
        push @buffer, $outer;
        last if eof(FH1);
    }

    # Seek code here

    while ($inner = <FH2>){
        for (@buffer) {
            [code to compare line];
        }
    }
}

2月25日星期五的附录 - 更详细的代码解决方案

我在一天结束时有一些额外的时间,所以我编写了一个版本的这个版本适用于名为test.txt的文件,其中包含每行1-10的数字。因此,我将buffersize配置为4,以便脚本执行4次传递而不是11次,就像您的原始方法一样。

use strict;
use warnings;
use autodie;

my $buffersize = 4; # 1000;
my $file = 'test.txt'; # '/arf/test.txt';

open my $fh1, '<', $file;
open my $fh2, '<', $file;

while (! eof($fh1)) {

    # Move fh2 to start of buffer
    my $startofbuffer = tell($fh1);
    seek($fh2, $startofbuffer, 0);

    # Build Buffer of entries to compare
    my @buffer = ();
    for (1..$buffersize) {
        chomp(my $outer = <$fh1>);
        print "buffer, $.\n";
        push @buffer, $outer;
        last if eof($fh1);
    }

    # Keep track of relative offset to start of buffer
    for (my $offset = 0; !eof($fh2); $offset++) {
        chomp(my $inner = <$fh2>);

        for my $i (0..$#buffer) {
            last if $i >= $offset;
            my $outer = $buffer[$i];
            print "Compare outer $outer to inner $inner\n";
        }
    }
}

输出如下

buffer, 1
buffer, 2
buffer, 3
buffer, 4
Compare outer 1 to inner 2
Compare outer 1 to inner 3
Compare outer 2 to inner 3
Compare outer 1 to inner 4
Compare outer 2 to inner 4
Compare outer 3 to inner 4
Compare outer 1 to inner 5
Compare outer 2 to inner 5
Compare outer 3 to inner 5
Compare outer 4 to inner 5
Compare outer 1 to inner 6
Compare outer 2 to inner 6
Compare outer 3 to inner 6
Compare outer 4 to inner 6
Compare outer 1 to inner 7
Compare outer 2 to inner 7
Compare outer 3 to inner 7
Compare outer 4 to inner 7
Compare outer 1 to inner 8
Compare outer 2 to inner 8
Compare outer 3 to inner 8
Compare outer 4 to inner 8
Compare outer 1 to inner 9
Compare outer 2 to inner 9
Compare outer 3 to inner 9
Compare outer 4 to inner 9
Compare outer 1 to inner 10
Compare outer 2 to inner 10
Compare outer 3 to inner 10
Compare outer 4 to inner 10
buffer, 5
buffer, 6
buffer, 7
buffer, 8
Compare outer 5 to inner 6
Compare outer 5 to inner 7
Compare outer 6 to inner 7
Compare outer 5 to inner 8
Compare outer 6 to inner 8
Compare outer 7 to inner 8
Compare outer 5 to inner 9
Compare outer 6 to inner 9
Compare outer 7 to inner 9
Compare outer 8 to inner 9
Compare outer 5 to inner 10
Compare outer 6 to inner 10
Compare outer 7 to inner 10
Compare outer 8 to inner 10
buffer, 9
buffer, 10
Compare outer 9 to inner 10

3月2日星期日的附录 - 基准

你的速度没有提高的报告让我有点好奇,所以我创建了一个小脚本来制作一些假数据:

use strict;
use warnings;
use autodie;

my $lines = shift or die "Missing line count\n";
die "Lines outside of range 1 - 1,000,000" if $lines < 1 or $lines > 1_000_000;

my $fakedata = 70;

my $filename = 'fd' . sprintf("%06d", $lines) . '.txt';

open my $fh, '>', $filename;

for my $i (1..$lines) {
    my $fake = join '', map {('a'..'z')[int rand 26]} (1..$fakedata);

    $fh->print("$i fake${fake}fake\n");
}

close $fh;

print "Created $filename\n";

1;

__END__

然后我编辑了上面提供的详细代码,以便它不输出任何调试声明,而是进行了非常基础的比较。对于行数为1_000,10_000和20_000的文件,这会产生以下结果。

                For Buffer size, show Time in sec and (# of File Reads)
                --------------------------------------------------------
File by lines   b = 1        b = 10      b = 100     b = 1k      b = 10k  
-------------   -----        ------      -------     ------      -------  
Lines = 1k      t = 1.54s    t = 0.35s   t = 0.22s   t = 0.21             
Size = 88k      r = (1001)   r = (101)   r = (11)    r = (2)              
Tests = 500k                                                              

Lines = 10k     t = 185s     t = 35s     t = 23s     t = 21.7s   t = 21.5s
Size = 899k     r = (10k)    r = (1k)    r = (101)   r = (11)    r = (2)  
Tests = 50m                                                               

Lines = 20k     t = 593s     t = 136s    t = 90s     t = 86s     t = 85.5s
Size = 1.8m     r = (20k)    r = (2k)    r = (201)   r = (21)    r = (3)  
Tests = 200m                                                              

正如您所看到的,缓冲甚至只有100,使脚本时间缩短了5或更多。这些脚本仍然需要很长时间,因为比较的数量等于N * N / 2.这是针对20k文件的2亿次测试,这就是为什么你可以看到执行该文件所花费的时间是4倍的原因。只要10k文件。

如果这些值保持为真,则250k长文件将比20k文件长156.25倍。在具有缓冲的最佳情况下,这相当于3.71小时,或者在没有缓冲的情况下相当于25.7小时。这些数字甚至假设比较的绝对最短时间,我认为你的数字可能比简单的旧/偶数测试更复杂。

不幸的是,你没有说出你的项目的性质和这些比较,所以我无法猜测其他可能的速度改进。如果我假设你的目标更多是排序的本质,那么就可以将你的比较减少到O(n log n)而不是O(N ** 2)。为了排序,我建议你将文件分成能够适应内存的组,使用perl排序对它们进行排序,然后使用合并排序来合并已排序的组。我不打算提供更详细的代码,因为它只是猜想你正在做的事情。

无论如何,祝你的项目好运。

相关问题