Anant
Anant

Reputation: 222

Perl6: Match elements in a list with another list

I have a list of numbers L. There is another list of numbers M. I need to return a list L' of numbers found in both L and M.

Edit: Mathematically, I am looking for Multiset intersection.

Example:

L = 3, 1, 4, 1, 5, 9, 2, 6
M = 9, 7, 1, 2, 1, 1
L' = 9, 1, 2, 1

I wrote the following code for that:

my @some-numbers = 3, 1, 4, 1, 5, 9, 2, 6;
my @to-match     = 9, 7, 1, 2, 1, 1;
my @matched;

my %histogram;
for @some-numbers -> $n { %histogram{$n}++ };

for @to-match -> $n {
    next if not defined %histogram{$n};
    if %histogram{$n} > 0 {
        push @matched, $n;
        %histogram{$n}--;
    }
};

say @matched;

While it achieves the purpose, I was wondering whether there was an idiomatic Perl6 way of doing this?

Some background: I have been trying to learn Perl6 and Python together, and solve the same puzzles in both the languages. Python offered a particularly pleasing solution for the above problem. To my beginner eyes at least :)

Upvotes: 6

Views: 320

Answers (3)

raiph
raiph

Reputation: 32414

Depending on the precise semantics you're looking for, Bag operations might be just the ticket:

my \L = 3, 1, 4, 1, 5, 9, 2, 6;
my \M = 9, 7, 1, 2, 1, 1;

.put with L.Bag ∩ M.Bag;

displays:

9 1(2) 2

This is the stringification of a Bag containing the three keys '9', '1', and '2' whose respective values (repeat counts) are the integers 1, 2, and 1.

To get Perl 6 to produce a list from a bag with each key repeated the number of times indicated by its associated value, use the .kxxv method:

.kxxv.put with L.Bag ∩ M.Bag;

displays:

9 1 1 2

(The mnemonic for the kxxv method is that it's k for "key" then xx in analogy with the xx repetition operator and finally v for "value". It sorta makes sense if you think about it.)

But perhaps a bag won't do. For example, perhaps the order of elements in the result matters -- you need 9 1 2 1 and not 9 1 1 2? I'll extend this answer if a bag isn't the right way to go.

Upvotes: 6

Curt Tilmes
Curt Tilmes

Reputation: 3045

You can do it with bags:

my $some-numbers = bag 3, 1, 4, 1, 5, 9, 2, 6;
my $to-match     = bag 9, 7, 1, 2, 1, 1;
my $matched      = $some-numbers ∩ $to-match;
say $matched;

Output:

bag(9, 1(2), 2)

You can turn the bag back into an array with .kxxv.

my @match-list = $matched.kxxv;
say @match-list;

Output:

[9 1 1 2]

(If you don't care about duplicates, use sets instead of bags.)

Upvotes: 7

Håkon Hægland
Håkon Hægland

Reputation: 40758

You can try this:

use v6;

my @some_numbers = 3, 1, 4, 1, 5, 9, 2, 6;
my @to_match     = 9, 7, 1, 2, 1, 1;
my %seen  = map { $_ => 1 }, @to_match;
my @matched = grep { %seen{$_}:exists }, @some_numbers;
say @matched;

Output:

[1 1 9 2]

Upvotes: 1

Related Questions