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

Hello Your Wisenessess,

Is there a smart way to check whether a number is within a range such as 0-255 using a regex without having to something ugly like:

^[0-9]{1,2}$|^1[0-9]{2}$|^2[0-4][0-9]$|^25[0-5]$

?

loris

Replies are listed 'Best First'.
Re: Regexp for range of numbers
by gellyfish (Monsignor) on Apr 05, 2005 at 09:02 UTC

    I'm sure you have your reasons for wanting to do this with a regular expression , but surely the smart way to do it would be:

    $number >= 0 and $number <= 255
    numeric comparison is going to be faster as that is something that computers can do natively.

    /J\

      Thanks for the reply. Actually I have other people's reasons for having to do this with a regexp. The regexp is an attribute in an XML file, which is at some point read by a Perl script.

      loris

Re: Regexp for range of numbers
by Corion (Patriarch) on Apr 05, 2005 at 09:06 UTC

    Of course, with 5.9.2, there is a much nicer way to construct an efficient regular expression to match the numbers from 0 to 255 without leading zeroes:

    use strict; my $zero_to_255 = join "|", 0..255; $zero_to_255 = qr(^(?:$zero_to_255)$); print $zero_to_255,"\n"; for (-2,-1, 0..259) { print "$_\n" unless /$zero_to_255/; };
      So, stringing together a somewhat large number of static ORs in a regular expression is efficient?
      • Programmer wise, I agree.
      • Space wise, not so much.
      • Time wise? If it is, it is counter intuitive to me...
      -Scott

        Please note that I mentioned 5.9.2, the most current release of the developer branch, which has a patch by demerphq, that builds a very optimized tree out of a list like mine. The tree (actually a Trie) can the optimally match/compare against the string, by looking at exactly the first three chars of the string and quickly decide if it matches or not.

Re: Regexp for range of numbers
by reneeb (Chaplain) on Apr 05, 2005 at 09:17 UTC
    Regexp::Assemble shows (?-xism:^(?:2(?:[6789]|5[012345]?|[01234]\d?)?|[3456789]\d?|1(?:\d\d?)?|0)$) as the RegExp for your problem...
Re: Regexp for range of numbers
by deibyz (Hermit) on Apr 05, 2005 at 10:10 UTC
    TIMTOWTDI, and I wanted to play with (?(condition)true-pattern|false-pattern) asserts.
    Looks like this:

    my $re =qr/ ^(\d{1,3})$ # Up to 3 digits (?(?{ ($1<256 and $1>=0) }) # If in the correct range .? # An allways match | # Else . # Never going to match after e +nd of string ) /x;
      You don't need to test against zero. Or put anything in the success-pattern; an empty pattern matches just fine.
      /^(\d{1,3})$(?(?{$1<256 }) | . )/x
      or you could move your anchor and use an impossible anchor for the fail-pattern:
      /^(\d{1,3})(?(?{$1<256 }) $ | ^ )/x

      Caution: Contents may have been coded under pressure.
        I tend to use (?!) for the always fail pattern (there are no positions that aren't followed by the empty string). ^ might match if a /m flag is in use, or if the zero-width assertion is done at the beginning of the string. An empty string works fine as an always match pattern - or (?=) if you want to go symmetric.
Re: Regexp for range of numbers
by simon.proctor (Vicar) on Apr 05, 2005 at 09:14 UTC
    To repeat a node above, doing this with regex seems a little odd. Anyway, I thought I would take 5 and have a go. I did mine with look behinds (cos I rarely get to use them and its different to your solution). Not sure if my attempt is bone headed :). Still . . . here it is:
    use strict; use warnings; my @nums; for(my $i = -100; $i < 400; $i++) { push @nums, $i; } foreach my $n (@nums) { if($n =~ /^[012]?(((?<=2)[0-5])|(?<!2)[0-9])?(((?<=25)[0-5])|(?<!2 +5)[0-9])$/) { print $n, ": pass\n"; } else { print $n, ": fail\n"; } }
    HTH
Re: Regexp for range of numbers
by Roy Johnson (Monsignor) on Apr 05, 2005 at 11:03 UTC
    Update: Don't use this. $1 comes from the previous match, not the current one. You would have to use experimental features for a solution in this vein.

    This reads a little nicer, but I don't know whether you'd consider it cheating. It's a Perl regex:

    /^(\d{1,3})${\($1 > 255 ? qr(^) : qr($))}/
    Three digits, then interpolate a scalar ref block that tests what has already matched and returns the rest of the regexp: if the number is too high, the pattern is impossible; otherwise, end-of-string.

    Update: meant to link to the post, not the poster. So now "solutions in this vein" links to the post.


    Caution: Contents may have been coded under pressure.
      Hey! I've just noticed you're calling me "experimental feature".
      Thanks!! =oD
      That doesn't work. Interpolation happens before the regex is performed, so your $1 refers to a previous succesful match:
      #!/usr/bin/perl use strict; use warnings; while (<DATA>) { chomp; printf "%d %s\n", $_, /^(\d{1,3})${\($1 > 255 ? qr(^) : qr($))}/ ? "matches" : "fails"; } __DATA__ 3 100 333 200 Use of uninitialized value in numeric gt (>) at XXX line 9, <DATA> lin +e 1. 3 matches 100 matches 333 matches 200 fails
        Yes, I've had a notice to that effect at the top of the post for about an hour.

        Caution: Contents may have been coded under pressure.
Re: Regexp for range of numbers
by Roy Johnson (Monsignor) on Apr 05, 2005 at 12:10 UTC
    My previous answer is wrong, because variables are interpolated at the time the regex is created. This relatively compact expression works, though:
    use strict; use warnings; my @nums = (100,-3,150,-2,-1,256, 280, 254);; foreach my $n (@nums) { print "$n: ", ($n !~ /^1?\d{1,2}$|^2(?:[0-4]\d|5[0-5])$/) ? 'fail' : 'pass'; }

    Caution: Contents may have been coded under pressure.
      That would match: "1\N{TIBETAN DIGIT ONE}\N{TIBETAN DIGIT ONE}", but not "\N{TIBETAN DIGIT ONE}1\N{TIBETAN DIGIT ONE}". Doesn't seem fair to the Dalai Lama to me!

      You need to use use charnames ':full' to use \N in the way presented.