在Perl中检查行中重复列的最快方法

时间:2019-02-22 18:53:30

标签: arrays perl

我有一个这样的文件,其中包含100万行

aaa,111
bbb,222
...
...
a3z,222 (line# 500,000)
...
...
bz1,444 (last line# 1 million)

我需要检查的是逗号后的第二个值是否唯一。如果没有,请打印出行号。在上面的示例中,它应该打印出来

Duplicate: line: 500000 value: a3z,222

为此,我正在使用perl并将第二列的值存储在数组中。如果在数组中找不到值,则将其添加到其中。如果该值已经存在,则将其打印出来。

问题是我使用的逻辑超级慢。它需要2-3个小时才能完成。有什么办法可以加快速度吗?如果不需要,我不想创建一个数组。我只想检查文件第2列中的重复值。

如果在批处理文件中有更快的处理方法,我可以接受。

这是我的工作代码。

# header
use warnings;
use DateTime;
use strict;
use POSIX qw(strftime);
use File::Find;
use File::Slurp;
use File::Spec;
use List::MoreUtils qw(uniq);

print "Perl Starting ... \n\n"; 

# Open the file for read access:
open my $filehandle, '<', 'my_huge_input_file.txt';

my $counter = 0;
my @uniqueArray;
# Loop through each line:
while (defined(my $recordLine = <$filehandle>))
{

  # Keep track of line numbers
  $counter++;


  # Strip the linebreak character at the end.
  chomp $recordLine;

  my @fields = split(/,/, $recordLine);
  my $val1=$fields[0]; 
  my $val2=$fields[1]; 

  if ( !($val2 ~~ @uniqueArray) && ($val2 ne "") )
  {
        push(@uniqueArray, $val2);
  }
  else
  {
    print ("DUP line: $counter - val1: $val1 - val2: $val2 \n");
  }

}

print "\nPerl End ... \n\n"; 

3 个答案:

答案 0 :(得分:3)

这是哈希的目的之一

use feature qw(say);

...

my %second_field_value;

while (defined(my $recordLine = <$filehandle>))
{
    chomp $recordLine; 
    my @fields = split /,/, $recordLine;

    if (exists $second_field_value{$fields[1]}) {
        say "DIP line: $. -- @fields[0,1]";
    }
    ++$second_field_value{$fields[1]};
}

这必将累积该字段的所有可能值。我们也可以存储有关被骗者的合适信息,具体取决于需要报告的内容。

请注意(最后读取的文件句柄的)行号在$. variable中;不需要$counter

请注意,对于一个表达式,可以在一个表达式中进行检查和标志/计数器设置

if ($second_field_values{$fields[1]}++) { say ... }  # already seen before

这是检查重复项时的惯用法。感谢ikegami提出来。通过使条件post-increment有效(这样检查将使用旧值完成,并且该块中的计数是最新的)。

我也必须评论智能匹配运算符(~~)。众所周知,它目前的形式存在很大的问题,并且实际上可以肯定它会发生重大变化,甚至更糟。因此,简而言之,我会说:不要使用它。带有代码的代码有可能在将来某个未指定的时间点中断,可能没有警告,并且可能会悄悄地中断。


有关性能和“计算复杂性”的注释,在注释中提到。

在每行的数组中搜索具有 O n m )复杂度(n行,{{1} }值),实际上是 O n 2 ),因为数组在每一行上都获得一个新值(所以 m = n-1 );此外,由于通常没有重复,因此会(实际上)在每一行中搜索整个数组。使用散列时,复杂度为 O n ),因为我们在每一行上都有固定时间的查找。

关键是所有有关输入大小的信息。对于几百行的文件,我们无法区分。在百万行的情况下,报告的运行时间为“ 2-3小时内的任意时间”(带数组)和“ 5秒内的”(带哈希)。

“复杂性”评估涉及投入量的事实具有实际意义。

对于一个人来说,具有粗心构建的算法的代码“运行得很好”可能会因意外的大输入而惨遭破坏,或者,一旦投入生产运行,就变成了真实的数据文件。

另一方面,使用更干净,更简单的代码运行通常会很令人满意,即使它的“复杂性”更差-当我们了解其用例时。

通常,复杂度告诉我们运行时如何取决于大小,而不是确切地取决于大小。因此, O n 2 )算法的运行速度可能比 O n log n )一个足够小的输入。这具有重要的现实意义,并广泛用于选择算法。

答案 1 :(得分:2)

使用哈希。数组适合于存储顺序数据,而散列则适合于存储随机访问数据。您对@uniqueArray的搜索在每次搜索中均为O(n),每行一次,因此算法为O(n ^ 2)。每次搜索的哈希解决方案将为O(1)(或多或少),每行执行一次,使其整体为O(n)。

另外,使用$.作为行号-perl会为您跟踪它。

my %seen;
while(<$filehandle>)
{
  chomp;
  my ($val1, $val2) = split /,/;

  # track all values and their line numbers.
  push @{$seen{$val2}}, [$., $val1];
}

# now go through the full list, looking for anything that was seen
# more than once.
for my $val2 (grep { @{$seen{$_}} > 1 } keys %seen)
{
  print "DUP line: $val2 was seen on lines ", join ", ", map { "$_[0] ($_[1]) " } @{$seen{$val2}};
  print "\n";
}

这都是O(n)。快得多。

答案 2 :(得分:0)

您接受的哈希答案将是此处的标准方法。但我想知道使用数组是否会更快一些。 (我也转而使用$_,因为它可以使代码更简洁。)

use feature qw(say);

...

my @second_field_value;

while (<$filehandle>))
{
    chomp; 
    my @fields = split /,/;

    if ($second_field_value[$fields[1]]) {
        say "DIP line: $. -- @fields";
    }
    ++$second_field_value[$fields[1]];
}

这将是一个相当稀疏的数组,但它可能仍比散列版本快。 (恐怕我没有时间对其进行基准测试。)

更新:我运行了一些基本测试。阵列版本 更快。但是还不够,值得担心。