Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

IF condition with a range

by Anonymous Monk
on Jul 16, 2018 at 14:12 UTC ( [id://1218582]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks,
I want to write the following into and IF clause: "If the number is exactly equal, or equal+1 or equal-1".
I guess I could write it like this:
if($x==$y or $x==$y-1 or $x==$y+1)

Is there a more compact way to write this?
Thanks!

Replies are listed 'Best First'.
Re: IF condition with a range
by ikegami (Patriarch) on Jul 16, 2018 at 14:13 UTC
      True! Thank you!

        You could also use

        if (abs($y-$x) <= 1)

        (Added to earlier post)

Re: IF condition with a range (updated)
by AnomalousMonk (Archbishop) on Jul 16, 2018 at 14:48 UTC
    ... "If the number is exactly equal, or equal+1 or equal-1".

    Your problem statement seems to assume integer operations: "If the number is exactly equal, or exactly equal+1 or exactly equal-1." The tests given by ikegami here are not quite equivalent to your OPed test if the numbers are not integers: (update: ikegami already assumed integers in his reply, but the following examples are still pertinent.) | If the numbers are not integers:

    c:\@Work\Perl\monks>perl -wMstrict -le "my $x = 2; my $y = 2.00000000000000123; print qq{$x $y}; ;; if ($x == $y or $x == $y-1 or $x == $y+1) { print 'in exact range'; } if ($y-1 <= $x && $x <= $y+1) { print 'in <= range'; } if (abs($y-$x) <= 1) { print 'in abs range'; } " 2 2 in <= range in abs range
    Remember that Perl numbers are essentially floating-point doubles. This may introduce some twists and pitfalls. (Update: Certain combinations of operations may produce a number like 2.00000000000000123 that may appear, in certain circumstances, to be an integer, but isn't. See What Every Computer Scientist Should Know About Floating-Point Arithmetic.) One way to force integer operations is with the integer pragma:
    c:\@Work\Perl\monks>perl -wMstrict -le "use integer; ;; my $x = 2; my $y = 2.00000000000000123; print qq{$x $y}; ;; if ($x == $y or $x == $y-1 or $x == $y+1) { print 'in exact range'; } if ($y-1 <= $x && $x <= $y+1) { print 'in <= range'; } if (abs($y-$x) <= 1) { print 'in abs range'; } " 2 2 in exact range in <= range in abs range

    Update: Changed both code examples to use the floating-point number 2.00000000000000123 (vice 2.5) for $y.


    Give a man a fish:  <%-{-{-{-<

      There are longdoubles and quadmath. With my perl (built with -Duselongdouble) I get:

      2 2.00000000000000123 in exact range in <= range in abs range

      Enjoy, Have FUN! H.Merijn

        Is this with or without the use of integer?


        Give a man a fish:  <%-{-{-{-<

      if (abs($y-$x) <= 1)

      It's perhaps worth noting that, with floats, if $x == $y it does not necessarily follow that $x - $y == 0, or that abs($x - $y) <= 1
      If $x and $y are both infinities (of the same sign) then the first condition holds true, while the second and third are false.

      Cheers,
      Rob

      The tests given by ikegami here are not quite equivalent to your OPed test if the numbers are not integers:

      I've already said as much in my post.

        Oops... I missed that on my first reading.


        Give a man a fish:  <%-{-{-{-<

Re: IF condition with a range
by tobyink (Canon) on Jul 16, 2018 at 16:21 UTC

    TIMTOWTDI.

    use Types::Common::Numeric 1.003_002 qw(IntRange); my $range = IntRange[ $y-1 => $y+1 ]; if ($range->check($x)) { ...; }
      use Types::Common::Numeric 1.003_002 qw(IntRange); my $range = IntRange[ $y-1 => $y+1 ]; if ($range->check($x)) { ...; }

      A Megabyte-sized package (uncompressed [Type-Tiny-1.002002.tar.gz), plus dependencies, plus a ton of objects generated just for a trivial range check? Isn't that a tiny bit too FAT?

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        The majority of the size of the distribution is the test suite. With nearly 1200 CPAN modules depending on Type::Tiny directly or indirectly, having a comprehensive test suite seems a good idea. Documentation and examples make up most of the remaining size.

        In terms of dependancies, the module Type::Tiny itself has no non-core dependancies (apart from a few modules that are bundled with it).

        $ perl -Ilib -MType::Tiny -E'say for sort keys %INC' Eval/TypeTiny.pm Exporter.pm List/Util.pm Scalar/Util.pm Type/Tiny.pm Type/Tiny/XS.pm Types/TypeTiny.pm XSLoader.pm feature.pm overload.pm overloading.pm strict.pm warnings.pm warnings/register.pm

        As you can see, they're all in core, except:

        • Eval::TypeTiny — not in core, but bundled with Type::Tiny
        • Type::Tiny::XS — loaded via eval { require Type::Tiny::XS } so it's an optional dependancy
        • Types::TypeTiny — not in core, but bundled with Type::Tiny

        The module I recommended, Types::Common::Numeric does have one additional non-core dependancy: Exporter::Tiny.

        Exporter::Tiny was originally called Exporter::TypeTiny and bundled as part of the distribution too. However, I was convinced by chocolateboy that it had merit as a standalone module, so I split it out as a separate CPAN distribution. Seems he was right as it now has nearly 4000 downstream dependants. I don't think the fact that some of the modules bundled with Type::Tiny (but not Type::Tiny itself) require Exporter::Tiny makes it unworthy of the "tiny" name.

        Yes, if it's just one range check in a script, requiring any module whatsoever seems like overkill. But realistically, problems like this don't come up in isolation. Most non-trivial programs will need to sanity-check data in loads of places — checking the data passed to object constructors, checking function and method arguments, etc. Type::Tiny and its bundled modules provide a framework for this which is more concise and usually more correct and faster than handwritten code.

        As an example of correctness, spot the problem with this code:

        use strict; use warnings FATAL => qw(all); sub set_age { my $self = shift; my ($age) = @_; croak "age cannot be negative" unless $age >= 0; $self->{age} = $age; return $self; }

        Okay, so the obvious thing is we're not checking $age is a number. But if we do $object->set_age("old") we'll get a warning about doing a numeric comparison on a string, so we're covered, right?

        Wrong. $object->set_age([]) will be allowed. Numeric operations on references silently act on their reference address without triggering any warnings.

        Rewriting it to this:

        use feature "state"; use strict; use warnings FATAL => qw(all); use Type::Params qw(compile); use Types::Standard qw(Object); use Types::Common::Numeric qw(PositiveOrZeroInt); sub set_age { state $check = compile(Object, PositiveOrZeroInt); my ($self, $age) = $check->(@_); $self->{age} = $age; return $self; }

        And as a bonus, it will test that $self is an object, and will check that @_==2.

        None of the other answers to the OP's question even bothered checking $x was actually a number. IntRange will check that it's defined, it's a non-reference, and it's an integer before checking that it's in the right range.

Re: IF condition with a range
by talexb (Chancellor) on Jul 17, 2018 at 12:46 UTC

    As a postscript to this .. if you weren't sure about how to implement this, a test script would allow you to try out a variety of approaches. That would give you the assurance that whichever approach you chose, you could be secure in the knowledge that you were getting the correct result.

    Tests are awesome.

    Alex / talexb / Toronto

    Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Re: IF condition with a range
by kcott (Archbishop) on Jul 17, 2018 at 10:43 UTC

    Assuming integers, and given your single and very specific test case, I think ++ikegami's second solution is probably the best.

    Here's a solution that's less compact and less efficient:

    if ($x =~ /^(?:${\join "|", $y-1 .. $y+1})$/)

    My test:

    $ perl -E 'my $y = 3; say "$_: ", /^(?:${\join "|", $y-1 .. $y+1})$/ ? + "YES" : "NO" for 1..5' 1: NO 2: YES 3: YES 4: YES 5: NO

    Having said that, given another test case with a larger range not centered exactly around $y, this technique may be more appealing. Consider

    if ($x==$y or $x==$y-1 or $x==$y-2 or $x==$y-3 or $x==$y+1)

    vs.

    if ($x =~ /^(?:${\join "|", $y-3 .. $y+1})$/)

    My test:

    $ perl -E 'my $y = 3; say "$_: ", /^(?:${\join "|", $y-3 .. $y+1})$/ ? + "YES" : "NO" for -1..5' -1: NO 0: YES 1: YES 2: YES 3: YES 4: YES 5: NO

    And those hard-coded numbers (in $y-3 and $y+1) could be variables: perhaps, $min_below_y and $max_above_y.

    So, as I said, in your very specific example, this technique wouldn't be the best choice; however, in a more general scenario, it could be a better choice.

    — Ken

Re: IF condition with a range
by mr_ron (Chaplain) on Jul 17, 2018 at 17:35 UTC

    Another approach if you want exact integer match. Not that much shorter but flexible for bigger range or more matches. See also Syntax::Keyword::Junction for more terse.

    use List::Util 'any'; if (any {$x == $_} $y, $y +1, $y -1)
    Ron
Re: IF condition with a range
by Anonymous Monk on Jul 16, 2018 at 18:25 UTC
      I was wondering, how do you go about in the following scenario?
      * if it is equal, do something
      * if it is equal or within +/- 1 do something else
      Because I think that, if you put these statements in sequential "ifs", then the second one does not find the exact equal matches (they are already greped in the first conditional if).

        You're thinking of something like this:

        if ( it is equal ) { do something; } elsif ( it is equal or close ) { do something else; }

        But you want:

        if ( it is equal ) { do something; } if ( it is equal or close ) { do something else; }

        Or even:

        if ( it is equal or close ) { if ( it is equal ) { do something; } do something else; }

        To build on tobyink's post, remember that it's easy to try things out:

        Updated with the Inf test case suggested by syphilis.

        use warnings; use strict; my @testcases = ( [1,-1], [1,0], [1,1], [1,2], [1,3], [9**9999,9**9999] ); for my $sub (qw/ doit1 doit2 doit3 doit4 /) { print "-----\n"; for my $testcase (@testcases) { print "$sub($testcase->[0], $testcase->[1]):\n"; # for this test only, not recommended otherwise: no strict 'refs'; &{$sub}(@$testcase); } } sub doit1 { my ($x,$y) = @_; if ($x==$y) { print "\tThey are equal, do something\n"; } if (abs($y-$x)<=1) { print "\tThey are within +/- 1, do something else\n"; } } sub doit2 { my ($x,$y) = @_; if ($x==$y) { print "\tThey are equal, do something\n"; } elsif (abs($y-$x)<=1) { print "\tThey are within +/- 1, do something else\n"; } } sub doit3 { my ($x,$y) = @_; if (abs($y-$x)<=1) { print "\tThey are within +/- 1, do something else\n"; } elsif ($x==$y) { print "\tThey are equal, do something\n"; } } sub doit4 { my ($x,$y) = @_; if (abs($y-$x)<=1) { if ($x==$y) { print "\tThey are equal, do something\n"; } print "\tThey are within +/- 1, do something else\n"; } } __END__ ----- doit1(1, -1): doit1(1, 0): They are within +/- 1, do something else doit1(1, 1): They are equal, do something They are within +/- 1, do something else doit1(1, 2): They are within +/- 1, do something else doit1(1, 3): doit1(Inf, Inf): They are equal, do something ----- doit2(1, -1): doit2(1, 0): They are within +/- 1, do something else doit2(1, 1): They are equal, do something doit2(1, 2): They are within +/- 1, do something else doit2(1, 3): doit2(Inf, Inf): They are equal, do something ----- doit3(1, -1): doit3(1, 0): They are within +/- 1, do something else doit3(1, 1): They are within +/- 1, do something else doit3(1, 2): They are within +/- 1, do something else doit3(1, 3): doit3(Inf, Inf): They are equal, do something ----- doit4(1, -1): doit4(1, 0): They are within +/- 1, do something else doit4(1, 1): They are equal, do something They are within +/- 1, do something else doit4(1, 2): They are within +/- 1, do something else doit4(1, 3): doit4(Inf, Inf):

Log In?
Username:
Password:

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

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

    No recent polls found