Equality of hashref objects

B

bill

I have a class that is implemented as a hashref, and furthermore,
the values for the corresponding hash are always scalars. I want
a sub to test for equality between any two instances of this class,
defined roughly as "two instances $x and $y are equal if $x->{
$field } and $y->{ $field } are equal for all instance fields
$field". I took a crack at this function, but it is so *ugly* that
I'm sure there must be a better way. I give my version below for
your mocking pleasure. Any pointers to a better implementation
would be much appreciated.

bill


# ...

BEGIN { # private subs
$_equals = sub {
my @args = @_[0, 1];

for my $field (@_fields) { # @_fields is defined earlier
my @vals = map $_->$field, @args;
my $n_defined = grep defined $_, @vals;
next if $n_defined == 0;
return 0 if $n_defined == 1;

if (2 == grep $_is_numeric($_), @vals) {
return 0 unless $vals[0] == $vals[1];
}
else {
return 0 unless $vals[0] eq $vals[1];
}
}
return 1;
};

$_is_numeric = sub {
# cribbed from a clpm post by Anno Siegel
use warnings 'all', FATAL => 'numeric';
return defined eval { $_[0] == 0 };
};
}

use overload '==' => $_equals, fallback => 1;

# ...
 
J

jl_post

bill said:
I have a class that is implemented as a hashref, and furthermore,
the values for the corresponding hash are always scalars. I want
a sub to test for equality between any two instances of this class,
defined roughly as "two instances $x and $y are equal if $x->{
$field } and $y->{ $field } are equal for all instance fields
$field".


If you have two hash references (say, $hashRef1 and $hashRef2), you
might be able to pull it off this way:


use Data::Dumper; # standard module; you should have it
print "Equal\n" if Dumper($hashRef1) eq Dumper($hashRef2);


I'm not positive that Dumper will return all the entries in the same
order for two equivalent hashes, so this MIGHT not work.

However, it still might be worth a shot. Try it out and see what you
get.

I hope this helps.

-- Jean-Luc
 
S

Steven Kuo

I have a class that is implemented as a hashref, and furthermore,
the values for the corresponding hash are always scalars. I want
a sub to test for equality between any two instances of this class,
defined roughly as "two instances $x and $y are equal if $x->{
$field } and $y->{ $field } are equal for all instance fields
$field". I took a crack at this function, but it is so *ugly* that
I'm sure there must be a better way. I give my version below for
your mocking pleasure. Any pointers to a better implementation
would be much appreciated.

bill

[ snipped ]



Look at the is_deeply function in Test::More,
or the functions from Test::Deep on CPAN. See
if they offer what you need.
 
N

nobull

bill said:
I have a class that is implemented as a hashref, and furthermore,
the values for the corresponding hash are always scalars. I want
a sub to test for equality between any two instances of this class,
defined roughly as "two instances $x and $y are equal if $x->{
$field } and $y->{ $field } are equal for all instance fields
$field". I took a crack at this function, but it is so *ugly* that
I'm sure there must be a better way. I give my version below for
your mocking pleasure. Any pointers to a better implementation
would be much appreciated.
Data::Compare

if (2 == grep $_is_numeric($_), @vals) {
return 0 unless $vals[0] == $vals[1];
}
else {
return 0 unless $vals[0] eq $vals[1];
}
$_is_numeric = sub {
use warnings 'all', FATAL => 'numeric';
return defined eval { $_[0] == 0 };
};

This has the effect of treating the strings '0' and '0000000000' as the
same. Is that what you wanted? What you you percieve as the problem
if you just use eq all the time?
 
S

Shawn Corey

bill wrote:
....
BEGIN { # private subs
^^^^^^^
What on Earth for? If these subs are useful inside the file, they are
useful outside the file. Stop reading books on programming and hack more
Perl.

--- Shawn
 
B

bill

In said:
bill wrote:
I have a class that is implemented as a hashref, and furthermore,
the values for the corresponding hash are always scalars. I want
a sub to test for equality between any two instances of this class,
defined roughly as "two instances $x and $y are equal if $x->{
$field } and $y->{ $field } are equal for all instance fields
$field". I took a crack at this function, but it is so *ugly* that
I'm sure there must be a better way. I give my version below for
your mocking pleasure. Any pointers to a better implementation
would be much appreciated.
Data::Compare
if (2 == grep $_is_numeric($_), @vals) {
return 0 unless $vals[0] == $vals[1];
}
else {
return 0 unless $vals[0] eq $vals[1];
}
$_is_numeric = sub {
use warnings 'all', FATAL => 'numeric';
return defined eval { $_[0] == 0 };
};
This has the effect of treating the strings '0' and '0000000000' as the
same. Is that what you wanted?

Yes, that was the intent.
What you you percieve as the problem
if you just use eq all the time?


In this application it is far more likely for a "numeric" field to
hold, say, '1.0' in one instance and '1.00' in another, than it is
for a "string" field to hold a value for which $_is_numeric would
return true. In fact, I am debating whether to change

return 0 unless $vals[0] == $vals[1];

to

return 0 unless abs($vals[0], $vals[1]) < $EPSILON;

for some tiny $EPSILON.

bill
 
A

Anno Siegel

bill said:
In <[email protected]>
bill wrote:
I have a class that is implemented as a hashref, and furthermore,
the values for the corresponding hash are always scalars. I want
a sub to test for equality between any two instances of this class,
defined roughly as "two instances $x and $y are equal if $x->{
$field } and $y->{ $field } are equal for all instance fields
$field". I took a crack at this function, but it is so *ugly* that
I'm sure there must be a better way. I give my version below for
your mocking pleasure. Any pointers to a better implementation
would be much appreciated.
Data::Compare
if (2 == grep $_is_numeric($_), @vals) {
return 0 unless $vals[0] == $vals[1];
}
else {
return 0 unless $vals[0] eq $vals[1];
}
$_is_numeric = sub {
use warnings 'all', FATAL => 'numeric';
return defined eval { $_[0] == 0 };
};
This has the effect of treating the strings '0' and '0000000000' as the
same. Is that what you wanted?

Yes, that was the intent.
What you you percieve as the problem
if you just use eq all the time?

In this application it is far more likely for a "numeric" field to
hold, say, '1.0' in one instance and '1.00' in another, than it is
for a "string" field to hold a value for which $_is_numeric would
return true. In fact, I am debating whether to change

Then ->new (or whatever) should take care that numeric fields are numeric
(by adding 0). Strings should also be normalized, if applicable. Usually.
return 0 unless $vals[0] == $vals[1];

to

return 0 unless abs($vals[0], $vals[1]) < $EPSILON;

for some tiny $EPSILON.

This sounds more and more like the wholesale approach "compare two hashes"
isn't the right one here. For general OO reasons, comparison should be
based on accessors, not on "wild" access to the underlying hash structure.
That some fields appear to need particular comparison routines is another
pointer in that direction. I'd say, the somewhat tedious

sub is_equal {
my ( $x, $y) = @_;
$x->some_field eq $y->some_field and
$x->other_field == $y->other_field and
abs( $x->third_field - $y->third_field) < EPS and
# ...
}

is the way to go. If there are very many fields some automation is
still possible.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,235
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top