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

Need help with reading each key and value from both lists at the same time so that they can be tested.

if ($sec58_flg == 1) { print "Section 58 Is Not Equal\n"; @keys_raw = sort { $a <=> $b } (keys %raw58); @keys_ref = sort { $a <=> $b } (keys %ref58); for @keys_raw; @keys_ref -> $key_raw; $key_ref { $val_raw = $raw58{$key_raw}; $val_ref = $ref58{$key_ref}; if ($key_raw eq $key_ref && $val_raw eq $val_ref) { next; } else { print "$key_raw $val_raw $key_ref $val_ref\n"; } } }

20041011 Janitored by Corion: Added code tags

Replies are listed 'Best First'.
Re: Multiple loop variable in foreach statement
by duff (Parson) on Oct 12, 2004 at 16:42 UTC

    It looks like you're iterating in parallel. Just access them by index:

    for my $i (0..$#keys_raw) { $val_raw = $raw58{$keys_raw[$i]}; $val_ref = $ref58{$keys_ref[$i]}; next if ($keys_raw[$i] eq $keys_ref[$i] && $val_raw eq $val_ref); print "$keys_raw[$i] $val_raw $keys_ref[$i] $val_ref\n"; }
      I sinserely appreciate the help you have provided!
Re: Multiple loop variable in foreach statement
by Roy Johnson (Monsignor) on Oct 12, 2004 at 16:47 UTC
    for my $i (0..(@keys_raw > @keys_ref ? $#keys_raw : $#keys_ref)) { my ($key_raw, $key_ref) = ($keys_raw[$i], $keys_ref[$i]); my ($val_raw, $val_ref) = ($raw58{$key_raw}, $ref58{$key_ref}); #... if-block as before }

    Caution: Contents may have been coded under pressure.
      I sinserely appreciate the help you have provided!
Re: Multiple loop variable in foreach statement
by tmoertel (Chaplain) on Oct 12, 2004 at 17:22 UTC
    A quick way to test hashes for equality is to test that they have identical sizes and, if so, that all of the key-value pairs from the first hash have equal corresponding pairs in the second. Other options are available in the perlfaq4 entry "How do I test whether two arrays or hashes are equal?"

    But, let's run with the first option. Here is a straightforward implementation:

    sub are_hashes_equal($$) { my ($a, $b) = @_; # hashrefs # number of keys in hashes must be the same return 0 unless keys %$a == keys %$b; # and each key-value pair in %$a must have an # identical key-value pair in %$b while (my ($key_a, $val_a) = each %$a) { return 0 unless exists $b->{$key_a} && $b->{$key_a} eq $val_a; } return 1; }
    (In your sample code, you printed out the key-value pairs that tested as unequal. I'm not sure whether you did that for debugging purposes or because your application really requires it. If the latter, you make the above code behave that way by replacing the first return 0 line with
    ($a,$b) = ($b,$a) if keys %$b > keys %$a
    then removing the final return 1, and finally replacing the remaining return 0 with the desired print statement.)

    Just to make sure our implementation really works, let's test it with Test::LectroTest. We assert that the following four properties hold:

    use Test::LectroTest; Property { ##[ h <- Hash( Int, Int ) ]## are_hashes_equal( $h, $h ) == 1; }, name => "equal hashes are recognized as equal"; Property { ##[ h <- Hash( Int, Int, length=>[1,] ) ]## my %h_diff = %$h; delete $h_diff{scalar each %$h}; # delete 1st key are_hashes_equal( $h, \%h_diff ) == 0; }, name => "differences in quantity of keys are detected"; Property { ##[ h <- Hash( Int, Int, length=>[1,] ) ]## my %h_diff = %$h; delete $h_diff{scalar each %$h}; # delete 1st key $h_diff{a} = 1; # replace with "a" key are_hashes_equal( $h, \%h_diff ) == 0; }, name => "differences in values of keys are detected"; Property { ##[ h <- Hash( Int, Int, length=>[1,] ) ]## my %h_diff = %$h; $h_diff{scalar each %$h}++; # increment 1st value are_hashes_equal( $h, \%h_diff ) == 0; }, name => "differences in values are detected";
    And do the properties hold for our implementation?
    1..4 ok 1 - 'equal hashes are recognized as equal' (1000 attempts) ok 2 - 'differences in quantity of keys are detected' (1000 attempts) ok 3 - 'differences in values of keys are detected' (1000 attempts) ok 4 - 'differences in values are detected' (1000 attempts)

    Cheers!
    Tom

    (Update: Added comment to make clear that the function takes two hashrefs.)

    P.S. Below is all of the code from this post, in ready to run format.

      Quick, but quickly wrong.

      It is easy for two hashes to be the same but have different scalar sizes. For instance:

      my %foo = 0..1000; delete $foo{2*$_} for 1..500; print scalar %foo; # prints "1/512" my %bar = (0, 1); print scalar %bar; # prints "1/8"
      but in fact %foo and %bar have the same exact key/value pairs.

      Furthermore as I just pointed out in Re: Redirecting STDOUT from internal function with 5.6.1 restrictions, prototypes in Perl generally don't do what you'd want them to do, and you are far better off not using them.

      UPDATE Big oops. I don't know why I thought I read scalar where it said keys. The prototype comment still stands though.

        tilly, thanks for your feedback, but I can't see where the problem is. Can you point out exactly where my code goes wrong?
        It is easy for two hashes to be the same but have different scalar sizes.
        And that's why my code doesn't compare the scalar values of the hashes but the length of their keys arrays.

        Regarding prototypes, I'll take a look at the link. But my code expects two hashrefs, not hashes.

        Cheers,
        Tom

        You say that I'm far better off not using prototypes here. What makes you say that in this case?

        The code expects two hashrefs. While a prototype of \%\% might arguably be better, I don't see an advantage to not having any prototype in this case. Moreover, I do see a disadvantage in that if somebody mistakenly passes something other than the required two and only two arguments, the error won't be caught at compile time.

        Thanks for any enlightenment you can share.

Re: Multiple loop variable in foreach statement
by pizza_milkshake (Monk) on Oct 12, 2004 at 17:03 UTC
    #!perl -l use strict; use warnings; my %ref = qw(a 1 b 2 c 3 d 4); my %raw = qw(a 1 b 2 c 4 e 4); my %keys; @keys{keys %ref, keys %raw} = 0; for (keys %keys) { unless (defined $raw{$_} && defined $ref{$_} && $raw{$_} eq $ref{$ +_}) { my $vraw = defined $raw{$_} ? $raw{$_} : "undef"; my $vref = defined $ref{$_} ? $ref{$_} : "undef"; print "$_: $vraw, $_: $vref"; } }

    perl -e"\$_=qq/nwdd\x7F^n\x7Flm{{llql0}qs\x14/;s/./chr(ord$&^30)/ge;print"

Re: Multiple loop variable in foreach statement
by pg (Canon) on Oct 12, 2004 at 16:28 UTC

    This should match each pair for you:

    for my $key_raw (@keys_raw) { for my $key_ref (@keys_ref) { .... } }

    Update:

    Hm... this is just in general. In your case, both arrays are sorted, so you should use something that has a better performance. You should always remember the last used index for the inner loop. For each iteration of the outter loop, the inner loop should start from there.

      Well, I tried this already and what appears to happen is that the for loops act independently and not as one because the below code should only report one time (I changed one character in one of the files that made up the hash tables). $val_raw = $raw58{$key_raw}; $val_ref = $ref58{$key_ref}; if ($key_raw eq $key_ref && $val_raw eq $val_ref) { next; } else { print "$key_raw $val_raw $key_ref $val_ref\n"; }