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

I've read through a number of nodes relating to comparing elements of two arrays. These snippets do what I need except I would like to print both of the elements that mismatch
my @array1 = qw(a b c e); my @array2 = qw(a b c d); my %hash; for my $key (@array2) { $hash{$key}++; } for my $key (@array1) { print "Fail: $key\n" unless $hash{$key}; }
This is fine but I'd also like to print the element from array2 which produces the mismatch
e.g.
Fail: array1 element is e, array2 element is d
I tried this and get an uninitalized value error in the print
for my $key (@array1) { print "Fail: array1 element is $key, array2 element is $hash{$key} +\n" unless $hash{$key}; }
Any help welcome.

Replies are listed 'Best First'.
Re: Print both elements in a compare
by Furple (Novice) on Nov 01, 2010 at 19:08 UTC
    The reason why you're seeing an error is because the test above (the code you have quoted) is checking for the existence of the value $hash{$key} which returns undefined if there's nothing there (IE false). In the second code snippet you're trying to access an element that may or may not exist. In the case that the value you're trying to access doesn't exist ($hash{$key}) then you're asking for perl to access a nonexistant value in a data structure. I think that's the error you're getting. What it sounds like you're trying to do is roll through both arrays simultaneously determining if there's a problem as you go along. you could to some degree combine both operations but it's not exactly clean. instead of:
    my %hash; for my $key (@array2){ $hash{$key}++; } for my $key (@array1){ print "Fail: %key \n" unless $hash{$key}; }
    Try putting them together something like:
    for my $key (@array2){ $hash{$key}++ print "Fail $key" unless grep($key, @array1); }
    Two notes about that however. The first is that you don't know from this what the key is in array 1 that has caused the error, you just know which key mismatches. Second, if you have an array of numbers and one of them is 0 you'd return a false value for that grep. What I'd really recommend is a destructive process on the arrays. If you really need to keep those two alive then you can copy them.
    my $hash; while (@array1){ my $var1 = shift @array1; my $var2 = shift @array2; if($var1 == $var2){ $hash{$var1}++; } else { print "Key mismatch array1 value $var1 != $var2 (array2 value)"; } }
    The one note about the above code is that if array1 and array2 are not the same length you're gonna have a fatal error and die.
      Ah OK I understand why it's happening now. I can stick a check in for the array length prior to running this to avoid the fatal error you are on about. Is there anyway to do this without a destructive process on the arrays ?
        Use my compare2(), or List::Compare or Array::Compare.
Re: Print both elements in a compare
by Marshall (Canon) on Nov 01, 2010 at 19:53 UTC
    I think there is a module Array::Compare which will do what you want. If not, adapt following to suit you needs. Shows a couple of methods, more variations are possible as this can get complicated. Most simple is version2 when arrays are of the same size and sort order (don't have to worry about some undefined value). The below will not "blow up" if array's are of different size, but result could wind up being hard to interpret.
    #!/usr/bin/perl -w use strict; my @a = qw(a b c e); my @b = qw(a b c d g); compare1 (\@a,\@b); compare2 (\@a,\@b); sub compare1 { my ($aref, $bref) = @_; my %a = map{$_=>0}@$aref; my %b = map{$_=>0}@$bref; foreach (keys %a) { $a{$_}++ if (exists ($b{$_})); } foreach (keys %b) { $b{$_}++ if (exists $a{$_}); } foreach (keys %a) { print "$_ does not exist in B\n" if !$a{$_}; } foreach (keys %b) { print "$_ does not exist in A\n" if !$b{$_}; } } sub compare2 { my ($aref, $bref) = @_; my @a = @$aref; my @b = @$bref; while (@a || @b) { my ($compa, $compb) = (shift @a, shift @b); $compa = 'undefined' if (!defined($compa)); $compb = 'undefined' if (!defined($compb)); if ($compa ne $compb) { print "$compa and $compb do not match\n"; } } } __END__ e does not exist in B g does not exist in A d does not exist in A e and d do not match undefined and g do not match
      Thanks for the information. I wanted to avoid using modules becuase I don't learn anything by doing it that way.
        Perl is a "get the job done" kind of S/W tool and many of the Perl modules do help. They aren't all good or wise to use in all situations. However, when they do some "grunt work" for you so that you can think about bigger problems, I figure that is just fine. But when 5 lines of code does all you need and exactly what you need, then that's a different story.
Re: Print both elements in a compare
by moritz (Cardinal) on Nov 01, 2010 at 18:51 UTC
    I tried this and get an uninitalized value error in the print

    Are you sure the warnings comes from that line? The line you've shown produces "Use of uninitialized value in concatenation" (and not print), so maybe a different line is to blame?

    Also I can recommend to use perl 5.10 or newer, it often tells you which variable was actually undefined.

    That said your piece of code can produce such warnings when @array1 contains undef elements - what do you want to do in that case?

    Perl 6 - links to (nearly) everything that is Perl 6.
      Unfortunately I am unable to upgrade to Perl 5.10 at the moment so need to find a solution for Perl 5.8.8
      I'm confused as to why the code produces the concatenation error as you've discovered.
      The question is how do I get it to print both sides of the mismatch and I admit I hadn't catered for undef elements. Can you advise me on how to cater for both of those issues using the code posted ?
        Can you advise me on how to cater for both of those issues using the code posted ?

        You can always use defined to check if an element is not undef, and do whatever you want with that information.

        Perl 6 - links to (nearly) everything that is Perl 6.
Re: Print both elements in a compare
by eff_i_g (Curate) on Nov 01, 2010 at 19:53 UTC
Re: Print both elements in a compare
by Anonymous Monk on Nov 01, 2010 at 18:59 UTC
    The hash doesn't hold an entry for 'e' because of the way you created it. And even if it did, it would be a count, not the corresponding element 'd'...

    If the array elements match up by index, why not compare them iterating over the arrays?

    for my $i (0..$#array1) { if ($array1[$i] eq $array2[$i]) { ... } }