选择性地将元素从阵列A推送到阵列C中,这些元素不存在于阵列B中

时间:2011-11-08 00:07:26

标签: arrays perl array-filter

我正在尝试生成一个名为@names的数组,其中包含allnames.txt中存在但不存在于somenames.txt中的人的名字。我的代码如下:

if(open(SKIPLIST, "somenames.txt")) {       
    @some = <SKIPLIST>;
}
close(SKIPLIST);

if(open(TESTLIST, "allnames.txt")) {        
    @all = <TESTLIST>;
}
close(TESTLIST);

foreach $name (@all) {
    $name =~ s/[\n\r]//mg;
    if (grep {$_ eq $name} @some) {
        #Do nothing
    }
    else {
        push(@names, $name);
    }
}

print "Leftover: @names";

allnames.txt的内容:

adam
jake
john
troy

somenames.txt的内容:

adam
john

实际输出:

Leftover: adam jake troy

预期产出:

Leftover: jake troy

有人可以解释为什么'亚当'仍在被推动?

4 个答案:

答案 0 :(得分:2)

"adam"已包含在结果中,因为您的@some数组仅包含"adam\n"。要修复它,只需执行

chomp @some, @all;

或者,如果你想对DOS换行符偏执,

s/[\r\n]+$// for @some, @all;
在你的主循环之前

。那你也不需要行

$name =~ s/[\n\r]//mg;

在循环中。


此外,如果您希望代码快速,您应该使用哈希而不是@some数组,如下所示:

my %some;
if (open SKIPLIST, "somenames.txt") {       
    while (my $name = <SKIPLIST>) {
        chomp $name;
        undef $some{$name};  # create the key $name in the hash %some
    }
    close SKIPLIST;
}

my @names;
if (open TESTLIST, "allnames.txt") {        
    while (my $name = <TESTLIST>) {
        chomp $name;
        push @names, $name unless exists $some{$name};
    }
    close TESTLIST;
}

print "Leftover: @names\n";

答案 1 :(得分:1)

问题是你的一些元素有尾随和/或前导空格(\ n或\ r),有些则没有。解决问题的最佳方法是在阅读文件后立即清理它们:

if(open(SKIPLIST, "somenames.txt")) {       
    @some = <SKIPLIST>;
    foreach (@some) { $_ =~ s/[\n\r]//mg; }
}
close(SKIPLIST);

if(open(TESTLIST, "allnames.txt")) {        
    @all = <TESTLIST>;
    foreach (@all) { $_ =~ s/[\n\r]//mg; }
}
close(TESTLIST);

foreach $name (@all) {
    if (grep {$_ eq $name} @some) {
        #Do nothing
    }
    else {
        push(@names, $name);
    }
}

print "Leftover: @names";

答案 2 :(得分:1)

问题在于,您要从TESTLIST获取的内容中删除新内容,而不是从SKIPLIST获取的内容中删除新内容。

我使用哈希而不是grep进行快速查找,因此我的代码更像是

my %some;
while (<SKIPLIST>) {
   s/\s+\z//;
   ++$some{$_};
}

my @names;    
while (<TESTLIST>) {
   s/\s+\z//;
   push @names, $_ if !$some{$_};
}

或者如果你想要一种函数式编程风格的东西,

use List::MoreUtils qw( apply );
my %some = map { $_ => 1 } apply { s/\s+\z//; } <SKIPLIST>;
my @names = grep !$some{$_}, apply { s/\s+\z//; } <TESTLIST>;

如果您有重复的名称,并且想要获得重复的名称,请将!$some{$_}更改为!$some{$_}++(在任一代码段中)。

答案 3 :(得分:0)

无需编写循环来迭代这两组名称。使用map和散列片可以更清楚地了解正在发生的事情。

use strict;
use warnings;

my $fh;

open $fh, '<', 'somenames.txt' or die $!;
chomp(my @some = <$fh>);

open $fh, '<', 'allnames.txt' or die $!;
chomp(my @all = <$fh>);

my %diff = map(($_ => 1), @all);
delete @diff{@some};

print join(' ', "Leftover:", keys %diff), "\n";