ocs has asked for the wisdom of the Perl Monks concerning the following question:

Hi fellows,

this is just a small thing. I have two arrays. One array (A) contains names from databases which I want to request. The other array (B) contains names from databases from array A I want to skip.
In short, I have one array containing elements which I want to delete from an other array, i.e. the difference quantity.

Here is the code example.

#!/usr/bin/perl -wl use strict; sub create_list { my @skip_dbs = @_; my @dbs = qw/fetch forward user smtp/; return map { my $map = $_; grep { !/$map/ } @skip_dbs } @dbs; } my @list = create_list("user", "smtp"); print "@list";

The output I expect:

fetch forward

The output I get:

user smtp user smtp smtp user

Where I am wrong?
Thanks in advance for any help :)

Replies are listed 'Best First'.
Re: Difference Quantity of two Arrays
by ikegami (Patriarch) on Jan 02, 2007 at 14:40 UTC

    The 1st pass through map allows "user" and "smtp". ("fetch" would be skipped.)
    The 2nd pass through map allows "user" and "smtp". ("forward" would be skipped.)
    The 3rd pass through map allows "smtp". ("user" is skipped.)
    The 4th pass through map allows "user". ("smtp" is skipped.)

    One solution (Set arithmetic using a hash):

    sub create_list { my %dbs = map { $_ => 1 } qw/fetch forward user smtp/; delete @dbs{@_}; return keys %dbs; }

    Another solution (Set arithmetic using a superposition):

    use Quantum::Superpositions; sub create_list { my $dbs = any qw/fetch forward user smtp/; my $skip = all @_; return eigenstates( $dbs ne $skip ); }

    Another solution (Set arithmetic using sets [seemed appropriate at the time]):

    use Set::Scalar qw( ); sub create_list { my $dbs = Set::Scalar->new( qw/fetch forward user smtp/ ); my $skip = Set::Scalar->new( @_ ); return ($dbs - $skip)->members(); }

    Update: Added second and third snippet.

      Thank you for your explanation.

      My Perl book says, that delete deletes a key value pair. But it doesn't tell me why I have to use @dbs{@_} instead of $dbs{@_} (which apparently does nothing) or %dbs{@_} (which throws a failure) to get the thing working. Can you explain? :)

        $ means you're working with one element.
        @ means you're working with multiple elements.

        @hash{@list}
        is a hash slice. It amounts to
        map { $hash{$_} } @list
        but can be used as an lvalue.

        $hash{@list}
        is the same as
        $hash{join($;, @list}}
        It's a means of doing Hash of Hashes without actually using Hash of Hashes. It's generally unused.

Re: Difference Quantity of two Arrays
by PreferredUserName (Pilgrim) on Jan 02, 2007 at 14:43 UTC
    Your problem is that that outer map() will return something for each element of @dbs. You probably wanted a grep() there, but even that is problematic.

    You should really be thinking "hash" when you want to check if an item is in a set:

    sub create_list { my @skip_dbs = @_; # declare the hash my %skippables; # populate the hash with the skippables $skippables{$_}++ for @skip_dbs; my @dbs = qw/fetch forward user smtp/; # return a list of each element of @dbs # that isn't in the skippables hash return grep { ! $skippables{$_} } @dbs; } my @list = create_list("user", "smtp"); print "@list";

      Thanks all for your answers and especially the explanation of ikegami.

      But what does the ++ after $skippables{$_} do?

      # populate the hash with the skippables $skippables{$_}++ for @skip_dbs;

        It's the increment operator.

        When dealing with numbers (or undef),
        $a++
        means
        $a = $a + 1
        except it won't issue a warning if $a is undefined.

Re: Difference Quantity of two Arrays
by PreferredUserName (Pilgrim) on Jan 02, 2007 at 14:47 UTC
    And for the heck of it, here's a minimal-magic version:
    sub create_list { my @skip_dbs = @_; # declare the hash my %skippables; # populate the hash with the skippables foreach my $skip_db (@skip_dbs) { $skippables{$skip_db} = 1; } my @dbs = qw/fetch forward user smtp/; # create a list of each element of @dbs # that isn't in the skippables hash my @good_dbs; foreach my $db (@dbs) { unless (exists $skippables{$db}) { push @good_dbs, $db } } return @good_dbs; } my @list = create_list("user", "smtp"); print "@list";
Re: Difference Quantity of two Arrays
by cdarke (Prior) on Jan 02, 2007 at 14:51 UTC
    How about using a hash? The main problem here is that any original order will be lost, but maybe that does not matter?
    #!/usr/bin/perl -wl use strict; sub create_list { my @skip_dbs = @_; my %hash; @hash{qw/fetch forward user smtp/} = undef; delete @hash{@skip_dbs}; return keys %hash } my @list = create_list("user", "smtp"); print "@list";
Re: Difference Quantity of two Arrays
by jettero (Monsignor) on Jan 02, 2007 at 14:40 UTC

    If the skiplist is small enough, I'd chose something like $skip_rule = join("|", @skip_dbs); $skip_rule = qr{^(?:$skip_rule)$}; and then return grep {$_ !~ $skip_rule} @dbs. If the skip list is huge, that might not be such a good idea.

    -Paul

      qr{...} is quotish so you could do $" = q{|}; $skip_rule = qr{^(?:@skip_dbs)$};, probably localising the change of $" inside a small code block.

      Cheers,

      JohnGG

Re: Difference Quantity of two Arrays
by polettix (Vicar) on Jan 02, 2007 at 15:21 UTC
    How about using the hash for the banned keys? Something along these UNTESTED lines:
    sub subtract { my ($whole_ref, $banned_ref) = @_; my %is_banned = map { $_ => 1 } @$banned_ref; return grep { ! $is_banned{$_} } @$whole_ref; } my @surviving = subtract([qw(fetch forward user smtp)], [qw(user smtp) +]);
    This will also preserve the ordering in the original "whole" array. Note that array references are passed to the subtract() sub, in order to be able to pass two arrays (the first with the "whole" list and the other with the "banned" one).

    Flavio
    perl -ple'$_=reverse' <<<ti.xittelop@oivalf

    Don't fool yourself.
Re: Difference Quantity of two Arrays
by Moron (Curate) on Jan 02, 2007 at 16:31 UTC
    a grep variant of the same thing
    my @b1 = (); push @b1, $_, 1 for @b; my %b = @b1; my @c = grep !$b{$_}, @a;

    -M

    Free your mind

Re: Difference Quantity of two Arrays
by throop (Chaplain) on Jan 02, 2007 at 15:37 UTC
    I was surprised that this wasn't in CPAN's List::MoreUtils. Seems like this is a fairly common need.

    throop

      This is a set operation (set difference), not a list operation.

        This is a set operation (set difference), not a list operation.
        Sure it is a set operation. As is uniq, or even max. You could want to do the same operation to hashkeys, or to a CSV string. But the most common way that the set-difference problem shows up is as the set difference between two lists.

        If not List::MoreUtils, I'm still surprised it isn't in one of the common modules.

        throop

      List::Compare works for this problem:

      use strict; use warnings; use List::Compare; my @dbs = qw(fetch forward user smtp); my @skip_dbs = qw(user smtp); my $lc = List::Compare->new(\@dbs, \@skip_dbs); my @list = $lc->get_unique; print @list;

      Cheers,

      Brent

      -- Yeah, I'm a Delt.