http://www.perlmonks.org?node_id=919422

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

Hello, enlightened Monks!

A few days ago, a colleague of mine faced a simple problem - he had a list containing all users, and another one, containing disabled users. He wanted to find active users.

This, of course is a problem of finding a difference between two arrays, and perlfaq4 shows a simple way of solving it. This is my initial solution, based upon the perlfaq info:

#!/usr/bin/perl -w use strict; my @list1 = (1, 2, 3, 4, 5); my @list2 = (2, 3, 4); my @diff; my %repeats; for (@list1, @list2) { $repeats{$_}++ } for (keys %repeats) { push @diff, $_ unless $repeats{$_} > 1; }

A short while after I send him this code, my colleague found another way of solving the problem. This is his approach:

#!/usr/bin/perl -w use strict; my @list1 = (1, 2, 3, 4, 5); my @list2 = (2, 3, 4); my %diff; @diff{ @list1 } = undef; delete @diff{ @list2 };

I'm wondering, is there another clever way of getting the difference between two arrays? Could it be done in some way using map or grep?

The second array is a subset of the first one, so this is not a general case of computing a difference between arrays.

Regards,
Luke

Replies are listed 'Best First'.
Re: Difference between two arrays - is there a better way?
by BrowserUk (Pope) on Aug 09, 2011 at 09:06 UTC
    is there another clever way

    Another way for sure. Whether it is "clever"?

    @list1 = (1, 2, 3, 4, 5); @list2 = (2, 3, 4);; @diff{ @list2 }= ();; print grep !exists($diff{$_}), @list1;; 1 5

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Difference between two arrays - is there a better way?
by JavaFan (Canon) on Aug 09, 2011 at 09:22 UTC
    Your initial solution relies on users not appearing twice in a list. And that there's no user listed only in the disabled users. I very much prefer the solution of your coworker, which does not rely on these dependencies.

    And if you insist on a map/grep solution:

    my %disabled = map {($_, 1)} @list2; my @active = grep {!$disabled{$_}} @list1;
    This solution doesn't rely on the dependencies mentioned above either.

      Thanks.

      Both dependencies will always be met in this particular case - as I mentioned, this was not a general problem.

      Still, I also prefer the second solution - it's not only more general than mine, but looks better, and seems more simple/elegant (although the aesthetic argument may not be as valid as the one you mentioned - that it is more general).

      I was simply wondering about other ways, in the spirit of TMTOWTDI. Thank you, and thanks to BrowserUk for both other solutions.

Re: Difference between two arrays - is there a better way?
by bart (Canon) on Aug 09, 2011 at 11:11 UTC
    Here is my own "clever way", and you can even use it on more than 2 lists.
    my %presence; my $b = 1; foreach my $ary (\@list1, \@list2, \@list3) { foreach(@$ary) { $presence{$_} |= $b; } } continue { $b *= 2; }
    Now you can grep in keys %presence. Each value will indicate in what lists the item was found, as a bit mask. Examples:
    @list1only = grep { $presence{$_} == 1 } keys %presence; @list2only = grep { $presence{$_} == 2 } keys %presence; @list3only = grep { $presence{$_} == 4 } keys %presence; @lists1and2only = grep { $presence{$_} == 1+2 } keys %presence; @inall = grep { $presence{$_} == 1+2+4 } keys %presence; # etc
Re: Difference between two arrays - is there a better way?
by Khen1950fx (Canon) on Aug 09, 2011 at 12:45 UTC
    Instead of a diff, try finding unique users---the users who are still active. Using List::Compare:
    #!/usr/bin/perl use strict; use warnings; use List::Compare; use Data::Dump qw(dump); my @list1 = qw(1 2 3 4 5); my @list2 = qw(2 3 4); my $lc = List::Compare->new(\@list1, \@list2); print "Disabled users: ", dump($lc->get_intersection), "\n"; print "Active users: ", dump($lc->get_unique), "\n";
Re: Difference between two arrays - is there a better way?
by stonecolddevin (Parson) on Aug 09, 2011 at 19:30 UTC
Re: Difference between two arrays - is there a better way?
by SimonSaysCake (Beadle) on Dec 04, 2014 at 16:57 UTC

    This one-liner (versus answers with hashes and inspired by another answer) is working for me but has not been very rigorously tested (as run on AS Perl 5.16.3):

    @foo = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); @bar = (0, 1, 2, 5, 7, 9, 11, 12, 13, 14); my @diff = grep {my $baz = $_; ! grep($_ == $baz, @bar)} @foo; print join(",", @diff); Gives: 3,4,6,8,10

    Switch the lists around in the nested greps to go from being "in foo but not also bar" to "in bar but not also foo".

    -Simon

      wow this is mind-bending! can you please go through it in detail?
        @cities = (qw(London Oslo Paris Amsterdam Berlin )); @visited = (qw(Berlin Oslo)); say "Still need to visit:", grep { ! ({ $_, 0 } ~~ @visited) } @cities;
      What if the 2 lists are array of hashes?

        Then I recommend you adapt the concept in perlfaq4 to your situation.