我有一个这样的文件,其中包含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";
答案 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]];
}
这将是一个相当稀疏的数组,但它可能仍比散列版本快。 (恐怕我没有时间对其进行基准测试。)
更新:我运行了一些基本测试。阵列版本 更快。但是还不够,值得担心。