Re: Range check with unordered limits
by syphilis (Archbishop) on Jul 11, 2022 at 00:50 UTC
|
$inRange = (($a <=> $x) * ($b <=> $x)) < 1;
Nice use of the spaceship operator !! I like that.
I would probably use <=0 rather than <1 because I feel that it better defines the condition ... though both are, of course, exactly the same.
One of my first reactions was "How come positive fractional values less than 1 are allowed ?" ;-)
(I note that if one wants to also exclude values that are equal to either of the limits, then it's just a matter of altering the condition to <0.)
In the *truly* general case, this method doesn't correctly allow for the possibility that $a, $b or $x could be NaN.
If ($a <=> $x) and/or ($b <=> $x) involve comparison to a NaN, then they return undef - and unfortunately undef is treated as zero in numeric context, so $inRange would be set to a true value, because 0 is less than 1.
Note: This failing applies only to the (usual) case where the range limiters are *inclusive*, but not if they are *exclusive*.
Cheers Rob | [reply] [d/l] [select] |
Re: Range check with unordered limits
by jwkrahn (Abbot) on Jul 10, 2022 at 22:21 UTC
|
$inRange = (($a - $x) * ($b - $x)) < 1;
| [reply] [d/l] [select] |
|
That would work in most cases. The original code would also work for floating point values, whereas this version would need to change to ... <= 0 to handle that correctly. Under `use integer` it could also give the wrong answer due to overflow. In the general case it would be no faster than the original code, and can be slower.
| [reply] [d/l] |
|
> whereas this version would need to change to ... <= 0
IMHO for a multiplying approach that's the best check, even for the <=> version.
Simply changing to ... < 0 would check for the open interval excluding the endpoints. And ... == 0 for endpoints only.
But half-open intervals like [$a,$b[ can't be covered with this approach.
So I'd rather stick with
or the newer chained version
and make sure the endpoints are previously swapped if necessary
- ($a,$b) = ($b,$a) if $b < $a;
update
and swapping can be made non-destructive by localizing it to a scope.
C:\tmp\e>perl
($a,$b) = (7,3);
my $x=5;
my $inside = do {
local ($a,$b) = ($b,$a) if $b < $a;
$a <= $x < $b ;
};
print "$x in [$a, $b]? $inside";
__END__
5 in [7, 3]? 1
C:\tmp\e>
(replace local with my for private vars)
| [reply] [d/l] [select] |
Re: Range check with unordered limits
by LanX (Saint) on Jul 10, 2022 at 20:00 UTC
|
Further reduced, I don't know.
But I find using explicit min and max adds more clarity, and newer Perls allow ternary° expressions "chained comparisons" a < x < b :
C:\tmp\e>perl
use List::Util qw( min max );
($a,$b) = (3,7);
print "$_ is inside: ", min($a,$b) <= $_ <= max($a,$b),"\n" for 2..8
__END__
2 is inside:
3 is inside: 1
4 is inside: 1
5 is inside: 1
6 is inside: 1
7 is inside: 1
8 is inside:
Using the spaceship that way is too clever for me.
°) not sure if that's the right term
update: corrected. see perlop#precedence for more. | [reply] [d/l] [select] |
Re: Range check with unordered limits
by hexcoder (Curate) on Jul 13, 2022 at 06:44 UTC
|
Dear monks, thanks for your insights and extensions!
I did this one-liner as a fun task (because i was too lazy to detect min and max values beforehand) while working with integer values.
I agree with syphilis and LanX that
$inRange = (($a <=> $x) * ($b <=> $x)) <= 0;
is the most canonical form, where <= emphasizes the inclusion of the limits while < would hint for the exclusion.
My previous attempt was
$inRange = ($x > $a == $x < $b) || $x == $a || $x == $b; (inclusive)
$inRange = ($x > $a == $x < $b); (exclusive)
which seemed less elegant, but the exclusive version might be faster.
Cheers, Heiko | [reply] [d/l] [select] |
|
$inRange = ($x > $a == $x < $b) || $x == $a || $x == $b; (inclusive)
Two thoughts:
1) Isn't that equivalent to:
$inRange = ($x >= $a == $x <= $b);
Update: Yes, it isn't ;-) Thanks LanX.
2) Is it safe to assume that ($x > $a) and ($x < $b) will return the same value whenever they are true ?
It probably is ... but the docs merely say that they will return a true value, with no explicit guarantee that the magnitude of the "true value" will always be the same.
Cheers, Rob | [reply] [d/l] [select] |
|
> 1) Isn't that equivalent to:
No, your version fails the limits when they are swapped
Here a generalized test suite for everyones experiments:
use v5.12;
use warnings;
our ($a,$b,$x);
for my $lim ( [2,4], [4,2] ) {
($a,$b) = @$lim;
for $x (1..5) {
my @r = (&s0,&s1);
unless ( $r[0] == $r[1] ) {
say "fails for $x in [@$lim] with <$r[0]>,<$r[1]>";
}
}
}
sub s0 {
($x > $a == $x < $b) || $x == $a || $x == $b;
}
sub s1 {
$x >= $a == $x <= $b;
}
c:/tmp/pm/clever_intervall.pl
fails for 2 in [4 2] with <1>,<>
fails for 4 in [4 2] with <1>,<>
| [reply] [d/l] [select] |
|
> I agree with ... LanX that ... is the most canonical form
Did I say this?
| [reply] |
|
IMHO for a multiplying approach that's the best check, even for the <=> version.
So not verbally, but you preferred this version, I understood (as I do now).
| [reply] |
|