Remove elements from one array found in another

B

Bryan

Hi, I need a function that will remove any elements of the second array
from the first. I found a function that does mostly what I want but its
not behaving the way I want in one case.

For example, if I have two arrays;

@a1 = (1, 2, 3, 4, 5);
@a2 = (2, 4);

@a1 = sub removeArrayElements(\@a1, \@a2);

This works just fine, and @a1 = (1, 3, 5);

But if @a1 is empty;
@a1 = ();
@a2 = (2, 4);

The function returns @a1 = (4, 2), when I would like it to return ().

I believe the basic functionality for this function is from the perl
cookbook, so maybe Im using the wrong thing (don't have the cookbook
handy). Or is this the right function and it just needs to be tweaked?

Thanks,
B

sub removeArrayElements {
my ($a, $b) = @_;
my @a = @$a;
my @b = @$b;
my (@diff,%count);
my (%HASH_A,%HASH_B);



# build hashes to remove redundant elements
@HASH_A{@a}=(); # hash slice, sets @a as keys of %HASH_A
@HASH_B{@b}=();



# set $count{$e} to 2 if the element exists in both hashes
# $count{$e} will be 1 if the element is unique
foreach my $e (keys %HASH_A,keys %HASH_B) { $count{$e}++ }



# writes the hash key (the element) to @diff if it is unique
foreach my $e (keys %count) {
push @diff, $e if ($count{$e} == 1);
}



return @diff;
}
 
C

Chief S.

Bryan said:
Hi, I need a function that will remove any elements of the second array
from the first. I found a function that does mostly what I want but its
not behaving the way I want in one case.

For example, if I have two arrays;

@a1 = (1, 2, 3, 4, 5);
@a2 = (2, 4);

Won't this do the trick?

$seen{$_} = 1 for @a2;
@a1 = grep ! $seen{$_}, @a1;
 
T

Tore Aursand

sub removeArrayElements {
my ($a, $b) = @_;
my @a = @$a;
my @b = @$b;
my (@diff,%count);
my (%HASH_A,%HASH_B);

# build hashes to remove redundant elements
@HASH_A{@a}=(); # hash slice, sets @a as keys of %HASH_A
@HASH_B{@b}=();

# set $count{$e} to 2 if the element exists in both hashes
# $count{$e} will be 1 if the element is unique
foreach my $e (keys %HASH_A,keys %HASH_B) { $count{$e}++ }

# writes the hash key (the element) to @diff if it is unique
foreach my $e (keys %count) {
push @diff, $e if ($count{$e} == 1);
}

return @diff;
}

Don't be so hard to yourself. As far as I can tell from your question,
this function should do (untested);

sub removeArrayElements {
my $a1 = shift;
my $a2 = shift;

my %hash;
$hash{$_}++ for ( @$a2 );

my @new = grep { not $hash{$_} } @$a1;
return \@new;
}
 
J

John W. Krahn

Bryan said:
Hi, I need a function that will remove any elements of the second array
from the first. I found a function that does mostly what I want but its
not behaving the way I want in one case.

For example, if I have two arrays;

@a1 = (1, 2, 3, 4, 5);
@a2 = (2, 4);

@a1 = sub removeArrayElements(\@a1, \@a2);
^^^
That is a syntax error.

This works just fine, and @a1 = (1, 3, 5);

But if @a1 is empty;
@a1 = ();
@a2 = (2, 4);

The function returns @a1 = (4, 2), when I would like it to return ().

I believe the basic functionality for this function is from the perl
cookbook, so maybe Im using the wrong thing (don't have the cookbook
handy). Or is this the right function and it just needs to be tweaked?


sub removeArrayElements {

[snip]

This will do what you want:

sub removeArrayElements {
my ( $orig, $remove ) = @_;
my %seen = map { $_ => 1 } @$remove;
@$orig = grep !$seen{ $_ }, @$orig;
}




John
 
G

Glenn Jackman

Bryan said:
Hi, I need a function that will remove any elements of the second array
from the first. I found a function that does mostly what I want but its
not behaving the way I want in one case.

[...]
sub removeArrayElements($$) {
my %tmp;
my @a = @{shift()};
my @b = @{shift()};
@tmp{@a} = (1) x @a;
delete @tmp{@b};
return keys %tmp;
}
 
B

Brian McCauley

Glenn Jackman said:
Bryan said:
Hi, I need a function that will remove any elements of the second array
from the first. I found a function that does mostly what I want but its
not behaving the way I want in one case.

[...]
sub removeArrayElements($$) {

What is the point of that prototype?

I can see why you could want a (\@\@) prototype to save the caller
putting \ in the call but I really can't see any use for ($$)
my %tmp;
my @a = @{shift()};
my @b = @{shift()};

Why are you making copies of the arrays?
@tmp{@a} = (1) x @a;
delete @tmp{@b};
return keys %tmp;
}

This is not order preseving. The OP said they wanted to remove
elements from an array. Since arrays are ordered then the
null-hypothesis should be that the OP wanted to preserve order.

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
B

Brian McCauley

Chief S. said:
Won't this do the trick?

$seen{$_} = 1 for @a2;
@a1 = grep ! $seen{$_}, @a1;

That does not remove elements from the array @a1.

That replaces the content of @a1 with a list which is the old content
of @a1 less those elements that also appeared in @a2.

Usually this distinction is not relevant but occasionally it may be.

To answer the OP's question more pedantically

my %seen;
undef $seen{@a2}; = 1 for @a2;
for my $i ( reverse 0 .. $#a1 ) {
if ( exists $seen{$a1[$i]} ) {
splice @a1,$i,1;
}
}

Deja vu anyone?

http://groups.google.com/[email protected]
http://groups.google.com/[email protected]
http://groups.google.com/[email protected]

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
C

Chief S.

Brian said:
That does not remove elements from the array @a1.

That replaces the content of @a1 with a list which is the old content
of @a1 less those elements that also appeared in @a2.

Usually this distinction is not relevant but occasionally it may be.

Is the relevance of the distinction related to the outcome -- that is,
sometimes the resulting @a1 will be incorrect using the grep() method?

Or is the relevance of the distinction related to process -- that is,
you need to actually remove the items from the list, rather than
building a new filtered list, because your larger programming logic is
structured a certain way?

Thanks for the clarification.
 
B

Brian McCauley

Chief S. said:
Is the relevance of the distinction related to the outcome -- that is,
sometimes the resulting @a1 will be incorrect using the grep() method?

Or is the relevance of the distinction related to process -- that is,
you need to actually remove the items from the list, rather than
building a new filtered list, because your larger programming logic is
structured a certain way?

Er... I don't get the distinction you are drawing :)

Basically it depends on if there are any references to elements of @a1
floating around.

my @a1 = qw/ cat dog rabbit /;
my @a2 = qw/ dog /;
my $r = \$a1[2]; # References 'rabbit'
my %seen;
$seen{$_} = 1 for @a2;
@a1 = grep ! $seen{$_}, @a1;
$$r = 'hare'; # Change 'rabbit' to 'hare'
print "@a1\n"; # cat rabbit

As I said above it's relatively rare that this is an issue.

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top