Выборочное перемещение элементов из массива A в массив C, которых нет в массиве B - PullRequest
1 голос
/ 08 ноября 2011

Я пытаюсь создать массив с именем @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 ]

2 голосов
/ 08 ноября 2011

"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 голос
/ 08 ноября 2011

Проблема в том, что вы удаляете символы новой строки из того, что вы получаете из 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{$_}++ (в любом фрагменте).

1 голос
/ 08 ноября 2011

Проблема в том, что некоторые из ваших элементов имеют пробел и / или начальный пробел (\ 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";
0 голосов
/ 09 ноября 2011

Нет необходимости писать циклы для перебора двух наборов имен. Использование 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";
...