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

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

Hello brethren,

I'm writing some test for a new module, and I need a way of telling whether two arbitrarily complex data structures are identical in content.

One of the tests involves persisting a structure and reading it back - expecting the results to be identical. I tried Data::Dumper, and this almost worked - alas, the order of key/value pairs inside hashes is not preserved, hence the strings are not identical.

One thought would be to use Storable, but Storable with knobs on (or in this case hooks :-) is what I am in fact testing.

I either want an equality test that copes properly with hashes: {a=>'foo',b=>'bar'} equals {b=>'bar',a=>'foo'}, or a safe, guaranteed serialisation module that presents hash keys sorted.

I'm quite capable of writing a deep equality routine, but just a tad reluctant to re-invent the wheel.

Apologies if this information is already in the monks database, but Super Search aint what it used to be :-(.

--rW

Replies are listed 'Best First'.
Re: Need a test for deep equality
by Corion (Patriarch) on Apr 20, 2002 at 10:59 UTC

    While implementing my module File::Modified (nee File::Dependencies - not yet on CPAN), I also wanted such a deep comparision method, but I didn't find one either (in Python, this would be as easy as struct_a == struct_b :-)). But I would prefer basing the thing on Data::Dumper or another existing wheel, as cyclic structures are a nasty thing to handle. On the problem of hash order, it would make sense to do a sort() on the keys of the hashes before writing them out, so that identical hashes produce identical strings - maybe that would be just a small patch to Data::Dumper.

    Update : Of course, somebody already invented this wheel, in Test::More by Michael Schwern. There is the routine is_deeply, which does a deep compare - maybe this would warrant extraction in its own module...

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
      maybe that would be just a small patch to Data::Dumper.

      Perhaps, but somehow I doubt it'll happen. First off you would have to do it twice, once in C and once in perl. Second off Data::Dumper is optimized for speed and the overhead of sorting, especially a tied structure, could get prohibitive. OTOH Data::BFDump does sort its keys, and IIRC so does Data::Dump.

      Yves / DeMerphq
      ---
      Writing a good benchmark isnt as easy as it might look.

Re: Need a test for deep equality
by andreychek (Parson) on Apr 20, 2002 at 15:43 UTC
    There is a module for this over at CPAN called Struct::Compare. From the description:

    Compares two values of any type and structure and returns true if they are the same. It does a deep comparison of the structures, so a hash of a hash of a whatever will be compared correctly.

    So, you just pass in two structures, and it returns true or false, based on whether or not the structures are equal.

    Hope that helps!
    -Eric
Re: Need a test for deep equality
by gav^ (Curate) on Apr 20, 2002 at 21:52 UTC
    You might want to look at Data::Compare:
    use Data::Compare; my $hr1 = {a=>'foo',b=>'bar'}; my $hr2 = {b=>'bar',a=>'foo'}; print "They are ", (Compare($hr1,$hr2) ? "the same" : "different"), "\ +n";

    gav^

Re: Need a test for deep equality
by Dog and Pony (Priest) on Apr 20, 2002 at 11:08 UTC
    Maybe this isn't possible, but you could use something like Tie::IxHash to have the hashes always return the same order of keys.

    Otherwise, perlfaq4 has examples on how to do this using FreezeThaw.

    Maybe one of these might be what you need.


    You have moved into a dark place.
    It is pitch black. You are likely to be eaten by a grue.
Re: Need a test for deep equality
by rinceWind (Monsignor) on Apr 21, 2002 at 09:49 UTC
    Update: What I didn't specify, but perhaps should have, is that parts of the data structure are blessed object references, and I want to compare the guts of the objects.

    Conclusion:

    ModuleConclusion
    Data::DumperHashes not consistently serialised
    Test::MoreOnly does a shallow comparison.
    Tie::IxHashSolves a different problem. Anonymous hashes cannot be tied
    FreezeThawOnly copes with shallow structures
    Struct::CompareDoes not handle references
    Recursive comparison by thraxilDoes not handle object references
    Data::CompareDoes not handle references

    Conclusion: either fix Data::Dumper or roll my own compare routine :-<.

    Update 2: I have been looking further at Test::More. There are numerous versions floating about, owing to the way in which people have used it to package up their tests in their own modules.

    The definitive Test::More on CPAN has a function eq_hash, which suffers from the same syndrome as Data::Dumper when it comes to ordering of keys.

    Update 3: simonm++ The module Class::MakeMethods::Utility::Ref does the business! Shame about the name - something as useful should not IMO have a module name 4 levels down, with no clue as to what it can do.

      I have to wonder if you could canoncalize the hashes. A simple "alphabetize the keys at each level" comes to mind. This could be expensive for large/deep hashes, but, it may be worth it to you.

      This is an old thread, which is probably the reason for this omission, but... Test::More has an is_deeply() that works for my purposes (no object references). The docs for Test::More also recommend other Perl modules that will handle object references, etc.

Re: Need a test for deep equality
by thraxil (Prior) on Apr 20, 2002 at 17:55 UTC
Re: Need a test for deep equality
by ides (Deacon) on Apr 21, 2002 at 22:26 UTC
    Maybe I am missing something here but is there a reason you just don't compare the hashes by hand? Using recursion if they are hashes of hashes,etc? Something along these lines?
    my %hash1 = { 'foo' => 1, 'bar' => 1 }; my %hash2 = { 'foo' => 1, 'bar' => 1 }; foreach my $key ( keys(%hash1) ) { die "Not equal" if $hash1{$key} != $hash2{$key}; }
    I think you may be making this harder than it needs to be, either that or I'm not understanding your problem fully.

    -----------------------------------
    Frank Wiles <frank@wiles.org>
    http://frank.wiles.org

Re: Need a test for deep equality
by willijar (Initiate) on Apr 22, 2002 at 12:03 UTC
    While there are many Perl functions that could do this I would recommend rolling your own specific to your application. It isn't really reinventing the wheel as every application and structure will probably require a different definition for equality. For example are two structures equal only if stored at the same location or if they contain the same data. WHat about when comparing the object contents. At what depth do yuo compare contents rather than identity. Similarly for vectors. In comparing Strings should they be case insensitive. etc. Can an integer be equal to a float with the same printed representation. It isn't a simple question with simple answers.
Re: Need a test for deep equality
by simonm (Vicar) on Apr 23, 2002 at 05:00 UTC
    Yes, I've got a module on CPAN that does this -- see the ref_compare function in Class::MakeMethods::Utility::Ref, based on some old code by David Muir Sharnoff. It should handle scalar values, plain hashes or arrays, or arbitrary nested data structures.

    -Simon

Re: Need a test for deep equality
by KILNA (Acolyte) on May 02, 2002 at 21:21 UTC
    Another option is Data::Denter as a serializer for comparissons. It supports blessed objects and references, in addition to being able to sort hashes by default. Persoanlly, I think the output is a lot easier on the eyes than trying to look at Dumper output too. If I remember right, it can even handle circular references.

    -- KILNA - pop music for cyborgs - http://www.kilna.com