Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
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: (6)
As of 2024-03-28 19:14 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found