Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Range check with unordered limits

by hexcoder (Curate)
on Jul 10, 2022 at 18:00 UTC ( [id://11145405]=CUFP: print w/replies, xml ) Need Help??

Suppose you want to check if an integer is inside a given range. That seems trivial, if the range minimum and maximum are known ahead.
$inRange = $a <= $x && $x <= $b;

But if you don't know which one is minimum and which one is maximum, the algorithm should be a bit more flexible. Here is where Perl's spaceship operator can help. I came up with:

$inRange = (($a <=> $x) * ($b <=> $x)) < 1;
where $a and $b are the unordered range limits and $x is the variable to be tested.

Could this be optimized (reduced) further? I would be interested to know.

Thanks, hexcoder

Replies are listed 'Best First'.
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
Re: Range check with unordered limits
by jwkrahn (Abbot) on Jul 10, 2022 at 22:21 UTC
    $inRange = (($a <=> $x) * ($b <=> $x)) < 1;

    This appears to give the same result:

    $inRange = (($a - $x) * ($b - $x)) < 1;

      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.

        > 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

        • $a <= $x and $x < $b
        or the newer chained version
        • $a <= $x < $b
        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)

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

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.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    °) not sure if that's the right term

    update: corrected. see perlop#precedence for more.

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

      $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
        > 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>,<>

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

      > I agree with ... LanX that ... is the most canonical form

      Did I say this?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        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).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://11145405]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (5)
As of 2024-04-19 22:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found