http://www.perlmonks.org?node_id=379034

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

Hi all!

I need a quick loop for scanning a hash and return true if all hash values equal each other.

Thanks

Replies are listed 'Best First'.
Re: scanning hash
by ysth (Canon) on Aug 01, 2004 at 09:06 UTC
    keys(%{{reverse %hash}}) <= 1
    From the inside out:
    • %hash in list context returns a list of key,value,key,value
    • reverse LIST in list context reverses the list
    • { LIST } returns a ref to an anonymous hash with the given list of keys and values
    • %{ HASHREF } gets the hash given the reference
    • keys %HASH in scalar context returns a count of the keys
    Because the anonymous hash is built with keys as the values of the original hash and values as the keys of the original hash, and identical later keys supercede earlier keys, it will have only one key for each unique value from the original hash. So if it ends up with 1 key all the values of the original hash were equal (and if it ends up with 0 keys, the original hash was empty, which may or may not count as having all values equal).

      That works, is brief, and hip. It is possibly not however the most efficient solution as it fails to utilize an iterative, fail fast approach and requires generating and manipulating un-used anon data stuctures. Given the task we can look at less of the hash if we simply fail fast as soon as we detect a non matching value. Depending on the context this may or may not matter. Still a very cool solution though. Wish I had of thought of it. What the hell, I will next time :-)

      print ident( { foo=>1, bar=>1, baz=>1 } ); sub ident { my $value = (values %{$_[0]})[0]; for (values %{$_[0]} ) { return 0 unless $value eq $_; } return 1; }

      cheers

      tachyon

        You'll have to extend that a bit so it properly deals with undef vs empty string.

        sub ident { my $value = ( values %{ $_[0] } )[0]; for ( values %{ $_[0] } ) { no warnings 'uninitialized'; return 0 unless !( defined $value xor defined $_ ) and ( $valu +e eq $_ ); } return 1; }

        It would be better if I could think of a way to skip the $value eq $_ test when both variables are undefined, and still have the entire expression be true, but I can't see a way to do that without an extra test for definedness on one of the values.


        Update after bageler's reply: this was wrong:

        return 0 unless ( defined $value xor defined $_ ) and ( $value eq $_ ) +;

        The condition will only ever be true when comparing an undef with an empty string, but in no other case, because the left expression is only true if the operands are unequal, ie if only one of them is defined. In that case, the right expression can only also be true if the defined value is an empty string.

        Makeshifts last the longest.

      I love that solution. Here it is in the form of a sub. Pass a reference to the hash into the sub. A return value of true means all values are equal. A value of false means there are some not-equal values.

      use strict; use warnings; my %goodhash = qw/one 1 two 1 three 1 four 1 five 1/; my %badhash = qw/one 1 two 2 three 2 four 2 five 2/; foreach my $testhash ( \%goodhash, \%badhash ) { print SameVals( $testhash ) ? "Good.\n" : "Bad.\n"; } sub SameVals { return keys( %{ { reverse %{ $_[0] } } } ) <= 1; }

      Note that this adds a hashref dereference. That's the  %{ $_[0] } part.


      Dave

      This code is incorrect. It will treat undefs and empty strings as the same.

      Makeshifts last the longest.

        Undefs and empty strings are equal. The OP wanted to check if all values are equal.

Re: scanning hash
by davido (Cardinal) on Aug 01, 2004 at 09:47 UTC

    I just wanted to add that ysth's solution can also be adapted in such a way that it can be applied to arrays.

    Determine if all elements in @array are equal.

    use strict; use warnings; my @goodarray = ( 1, 1, 1, 1, 1 ); my @badarray = ( 1, 2, 3, 4, 5 ); foreach my $aref ( \@goodarray, \@badarray ) { print SameVals($aref) ? "Good!\n" : "Bad!\n"; } sub SameVals { keys %{ { map { $_ => undef } @{ +shift } } } <= 1; }

    Enjoy!\n


    Dave

      This too suffers the same problem as Re: scanning hash: it treats undefs and empty strings as the same.

      Makeshifts last the longest.

Re: scanning hash
by BrowserUk (Patriarch) on Aug 01, 2004 at 11:16 UTC

    use List Util qw[ reduce ]; print 'They\'re all the same!' if reduce{ $a && $a eq $b ? $a : () } values %hash;

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
      Very nice. But assumes that all values are true. It would be fairly easy to make it work with 0 or "" values, but not so easy to make it work with undef.
Re: scanning hash
by ysth (Canon) on Aug 01, 2004 at 11:46 UTC
    Here's a scalable, undef-friendly solution, just for kicks:
    my $multiple_values; my ($k,$v); use Data::Dumper; $Data::Dumper::Useqq = 1; while (defined($k = each %hash)) { if ($v) { ++$multiple_values, last if Dumper($hash{$k}) ne $v; } else { $v = Dumper($hash{$k}) } }
    It does distinguish between strings and numbers that eq would say were the same, though.

      That is nice in the spirit of TMTOWTDI, but it's overengineered and inefficient. defined is a perfectly sufficient vehicle to write code that handles undef vs empty string correctly.

      Makeshifts last the longest.

Re: scanning hash
by wfsp (Abbot) on Aug 01, 2004 at 09:28 UTC
    #!/bin/perl5 use strict; use warnings; my %hash = ( key1 => 0, key2 => 0, key3 => 0); my %dups = reverse %hash; my $count = keys %dups; print "$count\n";
    $count = 1

    Beaten to the post of course. This is just in case you wanted to do it on two lines instead of one!

      Again, incorrect, like Re: scanning hash. This approach treats undefs and empty strings as the same.

      Makeshifts last the longest.