tags:

views:

641

answers:

5

Hello, I would like to match with RegExp a number between X and Y. Is that possible?

([0-9]+) will match any number, how could I do to match a number between, for instance, 110 and 2234?

+6  A: 

This is not the sort of thing regexes excel at. You will probably find it easier to ensure that you have the right number of digits /^([0-9]{3,4})$/ and then do further checks against the capture.

Chas. Owens
+1  A: 

It's possible allbeit not pretty.

\b(?:[1][1][0-9]|1\d{3}|223[0-4]|2[0-1]\d\d|2[0-2][0-3][0-4])\b

I emailed Phillip Hazel, the author of PCRE, in 2006 what he thought of math's in regex:

Perhaps this lies out of the scope of the project in your view: The ability to treat numbers as being numbers and not text, this would definitely be a worthwhile feature. Allowing you to do some basic math checks on matched digits, like: is the 2nd matched digit higher or lower, is the third digit a multiple off the 1st, and many more complicated cases I won't elaborate on just to get my point accross. Do you feel this exceeds the realm of textmatching?

to which I got the following reply:

Yes, I think I do, and also, it is not something that is available in Perl regular expressions. I know that PCRE does have some extensions from Perl, but nothing as major as that (you could perhaps hack something up using callouts, but that would be a bit ad hoc, and no doubt exceedingly messy!).

Philip

and I couldn't agree more now in `09. Just match all numbers and do number validation in whatever language you're doing the matching with.

Martijn Laarman
I was reluctant to fix the quote's spelling, but I don't want the wrong spelling memes to spread.
Svante
+9  A: 

According to Generate a Regular Expression to Match an Arbitrary Numeric Range, and after generating such a regex for your example at Regex_For_Range:

\b0*(1[1-9][0-9]|[2-9][0-9]{2}|1[0-9]{3}|2[01][0-9]{2}|22[0-2][0-9]|223[0-4])\b

would do the trick.

The process would be (still following that Regex generator):

First, break into equal length ranges:

110 - 999
1000 - 2234

Second, break into ranges that yield simple regexes:

110 - 199
200 - 999
1000 - 1999
2000 - 2199
2200 - 2229
2230 - 2234

Turn each range into a regex:

1[1-9][0-9]
[2-9][0-9]{2}
1[0-9]{3}
2[01][0-9]{2}
22[0-2][0-9]
223[0-4]

Collapse adjacent powers of 10: 1[1-9][0-9] [2-9][0-9]{2} 1[0-9]{3} 2[01][0-9]{2} 22[0-2][0-9] 223[0-4]

Combining the regexes above yields:

0*(1[1-9][0-9]|[2-9][0-9]{2}|1[0-9]{3}|2[01][0-9]{2}|22[0-2][0-9]|223[0-4])

Next we'll try factoring out common prefixes using a tree:
Parse into tree based on regex prefixes:

. 1 [1-9] [0-9]
+ [0-9]{3}
+ [2-9] [0-9]{2}
+ 2 [01] [0-9]{2}
+ 2 [0-2] [0-9]
+ 3 [0-4]

Turning the parse tree into a regex yields:

0*(1([1-9][0-9]|[0-9]{3})|[2-9][0-9]{2}|2([01][0-9]{2}|2([0-2][0-9]|3[0-4])))

We choose the shorter one as our result.

\b0*(1[1-9][0-9]|[2-9][0-9]{2}|1[0-9]{3}|2[01][0-9]{2}|22[0-2][0-9]|223[0-4])\b
VonC
A: 

Thanks all for your comments and replies. You've all been very kind :-)

Note: I need and regexp because i'm using it in .htaccess file for match documents.

Thanks.

barredo
+1  A: 

While you could do it with some absurd looking regex (as VonC answered), regex really isn't supposed to do this.. Why not defer the number checking to the redirected-to-script?

If numbers 110-2234 go to script1, and 1-109 go to script2, it would be much simpler to direct all numbers at a router script, and have it redirect to the correct location (via HTTP redirects)..

In .htaccess:

RewriteRule ^view/([0-9]+)/?$ router.php?page=$1 [L]

..then in router.php, something like:

<?PHP
if(
   int($_GET['page']) > 110 &&
   int($_GET['page']) < 2234
){
    header("Status: 301 Moved Permanently\nLocation: /script1");
}else{
    header("Status: 404 Not Found");
}
?>
dbr
I know that. I would like to make it in .htaccess and avoid loading php and just load the right static file :-)
barredo
But thanks for the answer!! :-)
barredo