Perl: the Markov chain saw PerlMonks

### check if 2 values are equal

by eXile (Priest)
 on Jan 24, 2006 at 18:44 UTC Need Help??

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

esteemed monks, I'm overheating my brain on a function that checks if 2 values are equal, so i don't have to update a certain column in a SQL database. There are some things I think I need to take care of (like 0 vs "" vs undef, which are different in SQL). I don't have the SQL-column definition available at compare time. My 'equals' function currently looks like this:
sub equals { my (\$val1, \$val2) = @_; # first test for undef-ness if ( ! defined \$val1 || ! defined \$val2 ) { return 1 if (! defined \$val1 && ! defined \$val2 ); return undef; #case one is undef, other is def } # have to do next line this because strings evaluate to 0 in == co +ntext ... if ( ! \$val1 && ! \$val2 ) { # then test for ""-ness return 1 if ( (\$val1 eq "") && (\$val2 eq "") ); return undef if ( \$val1 eq "" || \$val1 eq "" ); #case one is "" other is 0 # then test for 0-ness return 1 if ( (\$val1 == 0) && (\$val2 == 0) ); } # then test for equality using 'eq' return 1 if ( \$val1 eq \$val2 ); # then test for equality using '==' if ( int(\$val1) && int(\$val2) ) { # all other number cases are already covered by # above tests (eg. both 0, or both the same string) # this merely takes care of cases like: # 1000.0000 == 1000 return 1 if ( \$val1 == \$val2 ); } # couldn't find any, so i guess they are not equal return undef; }
This looks very bloated (and a candidate for thedailywtf ), and it feels like there must be bugs lurking in this.

I'm pretty sure this can be done a lot easier/better. Anybody got a better/smarter way to accomplish this type of comparing of 2 variables?

I'm sure somebody can come up with a nicer solution, that'll make me bang my head at my desk for at least an hour.

Replies are listed 'Best First'.
Re: check if 2 values are equal
by GrandFather (Saint) on Jan 24, 2006 at 20:45 UTC

The following code may do what you want:

use strict; use warnings; my @pairs = ( [undef, undef], [0, undef], [0, 0], ['', undef], ['', ''], [1, und +ef], [1, 1], [0, '0'], [0, 0], ['0', '0'], ['0e0', '0'], ['x', '0'], ['x', 0], ); for (@pairs) { my @pair = (@\$_); print defined \$pair[0] ? ">\$pair[0]<" : 'undef'; print ', ' . (defined \$pair[1] ? ">\$pair[1]<" : 'undef'); print ': ' . (equals (@\$_) ? "match\n" : "different\n"); } sub equals { my (\$lhs, \$rhs) = @_; #False if mix of defined and undef return 0 if defined \$lhs != defined \$rhs; # True if both undef return 1 if ! defined \$lhs && ! defined \$rhs; # False if different as numbers no warnings "numeric"; return 0 if (0 + \$lhs) != (0 + \$rhs); # False if different as strings return 0 if ('' . \$lhs) ne ('' . \$rhs); return 1; }

Prints:

undef, undef: match >0<, undef: different >0<, >0<: match ><, undef: different ><, ><: match >1<, undef: different >1<, >1<: match >0<, >0<: match >0<, >0<: match >0<, >0<: match >0e0<, >0<: different >x<, >0<: different >x<, >0<: different

DWIM is Perl's answer to Gödel
thanks very much, this does what i'd like it to do, and if i compare it to my orginal function it even takes care of the 'numeric 0' vs. 'empty string' inequality.
my @vals = (undef, 0, '0', 'aap', 1, 't',''); my @expl = ('undef','numeric 0','string 0', 'string "aap"', 'nu +mber 1', 'string t','empty string'); my (\$i,\$j); for (\$i=0; \$i <= scalar(@vals-1); \$i++) { for (\$j=0; \$j <= scalar(@vals-1); \$j++) { # equals 1 print "equals_org '\$expl[\$i]'\t'\$expl[\$j]'\t" . equals_org(\$va +ls[\$i],\$vals[\$j]) . "\n"; print "equals_new '\$expl[\$i]'\t'\$expl[\$j]'\t" . equals_new(\$va +ls[\$i],\$vals[\$j]) . "\n"; print "--\n"; } } sub equals_org { my (\$val1, \$val2) = @_; # first test for undef-ness if ( ! defined \$val1 || ! defined \$val2 ) { return 1 if (! defined \$val1 && ! defined \$val2 ); return undef; #case one is undef, other is def } # have to do next line this because strings evaluate to 0 in == co +ntext ... if ( ! \$val1 && ! \$val2 ) { # then test for ""-ness return 1 if ( (\$val1 eq "") && (\$val2 eq "") ); return undef if ( \$val1 eq "" || \$val1 eq "" ); #case one is "" other is 0 # then test for 0-ness return 1 if ( (\$val1 == 0) && (\$val2 == 0) ); } # then test for equality using 'eq' return 1 if ( \$val1 eq \$val2 ); # then test for equality using '==' if ( int(\$val1) && int(\$val2) ) { # all other number cases are already covered by # above tests (eg. both 0, or both the same string) # this merely takes care of cases like: # 1000.0000 == 1000 return 1 if ( \$val1 == \$val2 ); } # couldn't find any, so i guess they are not equal return undef; } sub equals_new { my (\$lhs, \$rhs) = @_; #False if mix of defined and undef return 0 if defined \$lhs != defined \$rhs; # True if both undef return 1 if ! defined \$lhs && ! defined \$rhs; # False if different as numbers no warnings "numeric"; return 0 if (0 + \$lhs) != (0 + \$rhs); # False if different as strings return 0 if ('' . \$lhs) ne ('' . \$rhs); return 1; }

I'm a bit confused. Shouldn't

>0e0<, >0<: different
be a match?

0e0 and 0 are different as a string and the same as a number. OP seems to like it evaluated as different. A solution involving looks_like_number is required to get the result you would like, else 'x' and 'y' would be a match (0 == 0).

DWIM is Perl's answer to Gödel
Re: check if 2 values are equal
by Corion (Pope) on Jan 24, 2006 at 18:52 UTC

I think the three rules you want for equality are:

Two items are regarded as equal if

• they are both undef
• they are string-equal
• they are numerically equal

By translating these three rules into code, I come up with the following:

sub equals { my (\$val1,\$val2) = @_; return (!defined \$val1 && !defined \$val2) || (\$val1 eq \$val2) || (\$val1 == \$val2) };

But in such cases it really pays off if you write a test suite that tests your current implementation, then write your routine from scratch again with the rules you have found, and then test the new routine against the old test cases.

The problem with this is that the numeric comparison could return false positives. Try doing a comparison of 'aa' == 'bb'. With -w on, this does issue warnings, however it will still return true.

That is why something like Fletcher's looks_like_number from Scalar::Util is needed.

I have not used it myself, so I can not attest to the accuracy of this function, however the code would be like this:

sub equals { my (\$val1,\$val2) = @_; return 1 if !defined \$val1 && !defined \$val2; if (looks_like_number(\$val1) && looks_like_number(\$val2)) { return \$val1 == \$val2; } else { return \$val1 eq \$val2; } }
I agree my explanation of what I regard equal could be a little better. Your function doesn't work for me because I have to distinguish between an undef and a numeric 0 for instance (your function says these are equal).

I'll work out a testmatrix and manually check what i regard equal or not, thanks for that suggestion.

I think you can do away with the third condition of (\$val1 == \$val2).

First, the eq should catch differences between numbers equally well as the ==. See Comparing values.

Secondly, the == would fail (under strict) if one of the values were a string.

For eq to work for numbers, you need to know that the values are being represented internally as numbers, for instance:

\$a = 1000; \$b = 1e3; print \$a eq \$b ? 1 : 0; > 1 \$b = "1e3"; print \$a eq \$b ? 1 : 0; > 0 \$b=\$b+0; print \$a eq \$b ? 1 : 0; > 1

My solution is wrong, but for different reasons. eq cannot replace ==:

\$a = "01000"; \$b = 1000; printf "\\$a eq \\$b : %s\n", \$a eq \$b; printf "\\$a == \\$b : %s\n", \$a == \$b;

use strict; has no bearing on comparisons. What you are thinking of is use warnings;, which will warn about non-numeric values being compared with ==.

I think you can do away with the third condition of (\$val1 == \$val2).

Tricky one. I thought the same at first.
But think of 0 and 0.0 or 0E0 - all numerically equal, but not sting equal.
Same goes for 1.1 and 1.10 and of course, there's more to think about.

Cheers, Sören

Your test function should return false if only one of the items is undef. You do not test for that.
Re: check if 2 values are equal
by davido (Cardinal) on Jan 24, 2006 at 23:37 UTC

Perl already knows how to do all the work for you. You can just use a hash in almost the same way you would assure uniqueness in a list.:

sub isequal { return 0 if ( grep{ !defined(\$_) } @_ ) != scalar @_; no warnings qw/uninitialized/; my %gadget; @gadget{ @_ } = (); return scalar keys %gadget == 1 ? 1 : 0; }

This works by creating a hash whos keys are the items being compared. If after creating the keys you have one element, you've got equality. If you have more than one element, you have inequality. The only tricky part is to be sure to silence the warning you get for making a hash key out of 'undef', and to deal with the fact that undef stringifies to ''.

Here is a test example:

use strict; use warnings; use diagnostics; sub isequal { return 0 if ( grep { ! defined( \$_ ) } @_ ) != scalar @_; no warnings qw/uninitialized/; my %gadget; @gadget{ @_ } = (); return scalar keys %gadget == 1 ? 1 : 0; } my @test = ( [ 0, 1 ], [ "1", "one" ], [ 0, "0" ], [ 0, undef ], [ 1, undef ], [ "hello", undef ], [ undef, undef ], [ 1, 1 ], [ "1", 1 ], [ "hello", "hello" ] ); foreach my \$items ( @test ) { my( \$left, \$right ) = @{ \$items }; my \$comp = isequal( \$left, \$right ); foreach( \$left, \$right ) { ! defined \$_ and \$_ = "undef"; } print "Testing \$left, \$right == ", \$comp ? "equal\n" : "not equal\ +n"; }

Update: Another advantage to this method that may not be immediately apparent is that it can be used to test a list of any number of items.

Enjoy!

Dave

thanks, I eventually used your solution, with a minor modification:
sub isequal { return 0 if ( grep { ! defined( \$_ ) } @_ ) == scalar @_; no warnings qw/uninitialized/; my %gadget; @gadget{ @_ } = (); return scalar keys %gadget == 1 ? 1 : 0; }
(changed the != into a == in the first line in the function)
Re: check if 2 values are equal
by Fletch (Chancellor) on Jan 24, 2006 at 18:53 UTC

Perhaps looks_like_number from Scalar::Util would be of use?

Re: check if 2 values are equal
by CountZero (Bishop) on Jan 24, 2006 at 21:32 UTC
I was thinking of something along the following lines:
use strict; use Scalar::Util qw/looks_like_number/; sub equal { my (\$first, \$second) = @_; if ( (! defined \$first and ! defined \$second) or (looks_like_number(\$first) and looks_like_number(\$second) and +\$first == \$second) or (defined \$first and defined \$second and \$first eq \$second) ) { return 1; } else { return 0; } } while (<DATA>) { my (\$first, \$second, \$result) = split /,/; chomp \$result; if (\$result == equal(\$first, \$second)) { print "OK: >\$first< >\$second< >\$result<\n"; } else { print "NOT OK: >\$first< >\$second< expected >\$result<; got >",e +qual(\$first, \$second),"<\n"; } } __DATA__ undef,undef,1 undef,,0 undef,0,0 ,,1 100,100,1 100,99,0 100,0100,1 100,1E2,1 100,100.00,1 1000,1_000,1 1000,1_000.00,1 1000,1 000,1 1000,1 000.00,1 string,string,1 string,another_string,0
It seems to work except for the "funny" numbers written with underscores or spaces.

CountZero

"If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Create A New User
Node Status?
node history
Node Type: perlquestion [id://525282]
Approved by Corion
Front-paged by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (4)
As of 2021-01-24 00:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
The STEM quote I most wish I'd made is:

Results (257 votes). Check out past polls.

Notices?