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

Dear Masters,
I have three arrays @arA; @arB; @arC;. What I intend to do is for every element of @arA; I would like to check it against element of @arB; and @arC;. I know that I can do it in two loops like this:
#!/usr/bin/perl -w use Data::Dumper; my @arA = qw( lion tiger dog cat snake); my @arB = qw( tiger dragon lion); my @arC = qw(dog phoenix); my %all; foreach my $elA (@arA) { foreach my $elB (@arB) { if ($elA eq $elB) { push @{$all{$elA}}, $elB." - from Array B"; } } } foreach my $elA (@arA) { foreach my $elC (@arC) { if ($elA eq $elC) { push @{$all{$elA}}, $elC ." - from Array C"; } } } print Dumper \%all; # Then do other thing with %all
I can only think of achieving that in two separate foreach block for A (one with B, another with C). Is there a way we can construct that with single loop (meaning even with B and C in single loop with A)? Any CPAN module that can help us with it?

Note also that there can be more array (D, E, F, ...) which we need to check for Array A.

---
neversaint and everlastingly indebted.......

Replies are listed 'Best First'.
Re: Selective Check of Multiple Arrays With Single Construct
by merlyn (Sage) on Apr 14, 2007 at 17:03 UTC
    Variables that are sequentially or similarly named are almost always a sign that they belonged in a single data structure. For example, if you had placed the data into a hash of arrayrefs:
    my %data; $data{A} = [qw(a data goes here)]; $data{B} = [qw(b data here too)]; $data{C} = [qw(and here is c data)];
    Then the code to compare A to both B and C could have just been a loop changing an index or key value. This is especially true if you're about to introduce D, E, F as you say in your last paragraph.

    For example, here's a quick way to invert the index, and figure out which elements each item is in (presuming simple keys):

    my %seen_in; for my $key (sort keys %data) { for my $value (values %{$data{$key}}) { $seen_in{$value} .= $key; } }
    For "data", this will reveal "ABC". For "goes" this will be "A", and so on.
Re: Selective Check of Multiple Arrays With Single Construct
by blokhead (Monsignor) on Apr 14, 2007 at 15:52 UTC
    Instead of doing doubly-nested loops where you check everything in A against everything in B, you can use the %all hash to do it all in one pass:
    use Data::Dumper; my @arA = qw( lion tiger dog cat snake); my @arB = qw( tiger dragon lion); my @arC = qw(dog phoenix); my %all; @all{@arA} = (); for (@arB) { next unless exists $all{$_}; push @{$all{$_}}, "$_ - from array B"; } for (@arC) { next unless exists $all{$_}; push @{$all{$_}}, "$_ - from array C"; } print Dumper \%all;
    If something from array A was not found in the other arrays, it will exist in %all with an undefined value. You can grep these out at the end if you prefer.

    It would be easy to make this extensible (for arrays D, E, F, etc) if @arB, @arC were named like @{$arrays{B}} and @{$arrays{C}}. I'll leave that to you.

    blokhead

Re: Selective Check of Multiple Arrays With Single Construct
by liverpole (Monsignor) on Apr 14, 2007 at 16:00 UTC
    Hi neversaint,

    First of all, you can take the duplicated foreach my $elA (@arA) { ... } loops and combine them into one:

    foreach my $elA (@arA) { foreach my $elB (@arB) { if ($elA eq $elB) { push @{$all{$elA}}, $elB." - from Array B"; } } foreach my $elC (@arC) { if ($elA eq $elC) { push @{$all{$elA}}, $elC ." - from Array C"; } } }

    Then, simplifying each inner loop would give you:

    foreach my $elA (@arA) { map { ($elA eq $_) and push @{$all{$elA}}, "$_ - from Array B" } @ +arB; map { ($elA eq $_) and push @{$all{$elA}}, "$_ - from Array C" } @ +arC; }

    And finally, if you want to get really fancy, you could abstract even further (and anticipate adding other arrays into the mix):

    #!/usr/bin/perl -w use strict; use warnings; use Data::Dumper; my @arA = qw( lion tiger dog cat snake ); my @arB = qw( tiger dragon lion ); my @arC = qw( dog phoenix ); my %all; # This is the list of arrays we want to compare against "arA", # where the key is the label, and the value is the corresponding # array reference. my $p = { 'from Array B' => \@arB, 'from Array C' => \@arC, }; foreach my $elA (@arA) { foreach my $lbl (keys %$p) { map { ($elA eq $_) and push @{$all{$elA}}, "$_ - $lbl" } @{$p- +>{$lbl}}; } } print Dumper %all; # Then do some other things with %all...

    I think that's about as concise as you'll get it.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Selective Check of Multiple Arrays With Single Construct
by GrandFather (Saint) on Apr 14, 2007 at 18:28 UTC

    You may find List::Compare helps, but I can't help thinking there is a bit of XY going on here. What is the bigger problem you are trying to solve?

    A cleaner and easier to maintain version of your sample code is:

    use strict; use warnings; use Data::Dumper; my @arA = qw(lion tiger dog cat snake); my @arB = qw(tiger dragon lion); my @arC = qw(dog phoenix); my @arD = qw(lion wolf mouse); my %all; my %arrays = (b => \@arB, c => \@arC, d => \@arD); my %aElts; @aElts{@arA} = (1) x @arA; for my $array (keys %arrays) { for my $elt (@{$arrays{$array}}) { next unless $aElts{$elt}; push @{$all{$elt}}, "from Array $array"; } } print Dumper \%all;

    Prints:

    $VAR1 = { 'lion' => [ 'from Array b', 'from Array d' ], 'dog' => [ 'from Array c' ], 'tiger' => [ 'from Array b' ] };

    DWIM is Perl's answer to Gödel
Re: Selective Check of Multiple Arrays With Single Construct
by roboticus (Chancellor) on Apr 15, 2007 at 13:54 UTC
    neversaint:

    You've got several very good responses. Now here's a craptacular bit of code:

    #!/usr/bin/perl -w use strict; use warnings; my @arA = ('alpha', 'beta', 'delta', 'omega'); my @arB = ('alpha', 'foo', 'bar', 'omega', 'baz', 'beta'); my @arC = ('delta', 'grond', 'floo', 'delta', 'omega'); my @all = (); for my $elA (@arA) { my $idx=0; for my $elTest (@arB, @arC) { ++$idx; next unless $elA eq $elTest; if ($idx <= @arB) { push @all, $elTest . " - from Array B"; next; } $idx -= @arB; if ($idx <= @arC) { push @all, $elTest . " - from Array C"; next; } $idx -= @arC; # ... etc. } } print join("\n",@all);
    ...roboticus