I suggest the following regular expression:
(?<number>[1-9]|[12]\d|3[01]){0}(?<thing>\g<number>-\g<number>|\g<number>){0}^(\g<thing>,)*\g<thing>$
It looks terrible, but it is not. In fact, the construction (?<name>...){0}
allows us to define a named regular expression and say that it does not match where it is defined. So I defined a pattern for numbers named number
and a pattern for what I called a thing, that is, a range or number called thing
. Then I know that your expression is a sequence of these things, so I use the name regex thing
to create it with the \g<thing>
construct. It gives (\g<thing>,)*\g<thing>
. It is easy to read and understand. If you allow spaces to be inconsequential in your regular expression, you can even backtrack like this:
(?<number>[1-9]|[12]\d|3[01]){0} (?<thing>\g<number>-\g<number>|\g<number>){0} ^(\g<thing>,)*\g<thing>$/
I tested it with Ruby 1.9.2. Your regex engine must support these groups to provide such clarity.
irb(main):001:0> s1 = '1-5,5,15-29' => "1-5,5,15-29" irb(main):002:0> s2 = '1,28,1-31,15' => "1,28,1-31,15" irb(main):003:0> s3 = '15,25,3' => "15,25,3" irb(main):004:0> s4 = '1-24,5-6,2-9' => "1-24,5-6,2-9" irb(main):005:0> r = /(?<number>[1-9]|[12]\d|3[01]){0}(?<thing>\g<number>-\g<number>|\g<number>){0}^(\g<thing>,)*\g<thing>$/ => /(?<number>[1-9]|[12]\d|3[01]){0}(?<thing>\g<number>-\g<number>|\g<number>){0}^(\g<thing>,)*\g<thing>$/ irb(main):006:0> s1.match(r) => #<MatchData "1-5,5,15-29" number:"29" thing:"15-29"> irb(main):007:0> s2.match(r) => #<MatchData "1,28,1-31,15" number:"15" thing:"15"> irb(main):008:0> s3.match(r) => #<MatchData "15,25,3" number:"3" thing:"3"> irb(main):009:0> s4.match(r) => #<MatchData "1-24,5-6,2-9" number:"9" thing:"2-9"> irb(main):010:0> '1-1-1-1'.match(r) => nil