onlyf
onlyf

Reputation: 883

Perl - Filter function for arrays

i am trying to create a subroutine that does the following :

  1. Takes two arrays as input (Filter, Base)
  2. Outputs only the values of the second array that do not exist in the first

Example :

@a = ( 1, 2, 3, 4, 5 );
@b = ( 1, 2, 3, 4, 5, 6, 7);

Expected output :   @c = ( 6, 7 );
Called as : filter_list(@filter, @base)

###############################################

sub filter_list {
    my @names = shift;
    my @arrayout;
    foreach my $element (@_)
    {
       if (!($element ~~ @names )){
       push @arrayout, $element;
    }
    }
   return @arrayout
}

Test Run :

@filter = ( 'Tom', 'John' ); 
@array = ( 'Tom', 'John', 'Mary' );
@array3 = filter_list(@filter,@array);
print @array3;
print "\n";

Result :

JohnJohnMary

Can anyone help? Thank you.

Upvotes: 1

Views: 2256

Answers (3)

zdim
zdim

Reputation: 66883

This uses List::Compare, a module with a large collection of routines for comparing lists.

Here you want get_complement

use warnings;
use strict;

use List::Compare;

my @arr1 = ( 1, 2, 3, 4, 5 );
my @arr2 = ( 1, 2, 3, 4, 5, 6, 7);

my $lc = List::Compare->new(\@arr1, \@arr2);

my @only_in_second = $lc->get_complement;

print "@only_in_second\n";

The module has many options.

If you don't need the result sorted, pass -u to the constructor for faster operation.

There is also the "Accelerated Mode", obtained by passing -a. For the purpose of efficient repeated comparisons between the same arrays many things are precomputed at construction. With this flag that is suppressed, which speeds up single comparisons. See List::Compare Modes.

These two options can be combined, List::Compare->new('-u', '-a', \@a1, \@a2).

Operations on three or more lists are supported.

There is also the functional interface, as a separate List::Compare::Functional module.

Upvotes: 1

ikegami
ikegami

Reputation: 385764

You can't pass arrays to subs, only scalars. So when you do

my @filtered = filter_list(@filter, @base);

you are really doing

my @filtered = filter_list($filter[0], $filter[1], ..., $base[0], $base[1], ...);

As such, when you do

my @names = shift;

you are really doing

my @names = $filter[0];

which is obviously wrong.

The simplest solution is to pass references to the arrays.

my @filtered = filter_list(\@filter, \@base);

A hash permits an efficient implementation (O(N+M)).

sub filter_list {
   my ($filter, $base) = @_;
   my %filter = map { $_ => 1 } @$filter;
   return grep { !$filter{$_} } @$base;
}

Alternatively,

my @filtered = filter_list(\@filter, @base);

could be implemented as

sub filter_list {
   my $filter = shift;
   my %filter = map { $_ => 1 } @$filter;
   return grep { !$filter{$_} } @_;
}

Upvotes: 5

Schwern
Schwern

Reputation: 164809

What you're looking for is the difference of two sets. This, along with union, intersection, and a bunch of others are set operations. Rather than writing your own, there's plenty of modules for dealing with sets.

Set::Object is very fast and featureful. I'd avoid using the operator interface (ie. $set1 - $set2) as it makes the code confusing. Instead use explicit method calls.

use strict;
use warnings;
use v5.10;

use Set::Object qw(set);

my $set1 = set(1, 2, 3, 4, 5);
my $set2 = set(1, 2, 3, 4, 5, 6, 7);
say join ", ", $set2->difference($set1)->members;

Note that sets are unordered and cannot contain duplicates. This may or may not be what you want.

Upvotes: 1

Related Questions