in reply to Multiple loop variable in foreach statement

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.

#!/usr/bin/perl # Tom Moertel <tom@moertel.com> 2004-10-12 use warnings; use strict; sub are_hashes_equal($$) { my ($a, $b) = @_; # 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; } # we use LectroTest to test whether the following # properties hold for our implementation above 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";

Replies are listed 'Best First'.
Re^2: Multiple loop variable in foreach statement
by tilly (Archbishop) on Oct 12, 2004 at 17:34 UTC
    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

        Ah, my misreading. Sorry.
      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.

        I say that you are better off avoiding prototypes in general. Any individual use is a small deal, it is when you put them together that you get the problem.

        The problem is that your APIs become more complex, and more potentially surprising to someone else (who might be you, later). For instance perhaps sometimes in this situation you use $$ as a prototype, and in others you use \%\%. When you're coding, are you going to remember which you're in the habit of using? Maybe not. So sure, you know that you need 2 arguments, but to be sure that you aren't messing up you need to actually run the code, to be sure that you didn't write %foo when you needed to write \%foo, so it got coerced wrong. And then you run across code where someone passes in %foo, but it is going into a list of key/value pairs and you don't realize that. Or vice versa code where you thought it was supposed to do that but instead it got coerced. Or even code where you meant it to be coerced but the function got defined later than you thought and so no coercion took place.

        And when similar functions have different prototypes, then you get into the issue given at printf vs. sprintf. That can cause confusion in the unsuspecting for a while.

        And then we get the learning curve of things that do not scale well as you get to more sophisticated programming techniques. For instance if you go to OO programming, all of the effort that you spend thinking about prototypes goes out the window since they get ignored. But you or your maintainance programmer might not realize that, and can creatively go wrong for a while before finding it out. Likewise if you start playing with closures, wrapping functions, etc, you'll run into the situation that you cannot easily wrap a generic function without worrying about prototype issues.

        All little errors. All easy to solve individually. But a constant annoyance that doesn't go away.

        The alternative is to not use prototypes at all. Then you practice using a consistent argument passing style. The compile-time check is not a big deal - you do test code, don't you? You can then explore the joys of functions that accept arbitrary lists (I like passing in a hash). You get to use the same style for both functions and methods. And there is less typing.

        Perhaps you find this less than convincing. But go through previous threads on the topic and see how many competent people have come to the same conclusion that I have. There is a reason for that. Perl's version of prototypes do not buy you very much, and in the long run they impose a much larger cost than you realize.