Python正则表达式:使用分隔符匹配一系列数字

时间:2014-02-02 14:41:25

标签: python regex

我希望用户提供一系列要打印的数字: E.g:

27-84

我想出了以下正则表达式:

^(?P<Begin>\d+)((-?)(?P<end>\d+)?)$

因此它将匹配:27-84作为以下组:

Begin   [0-2]   `27`
2.  [2-5]   `-84`
3.  [2-3]   `-`
end [3-5]   `84`

似乎没问题,但我想知道是否有更优雅的方式,或者是否有我正在监督的空洞。

澄清:

  • 允许使用单个数字:27
  • 27-的单个号码应理解为27 to end
  • -10的单个号码应理解为begin to 10
  • 10-27的序列应理解为包含10到27。

2 个答案:

答案 0 :(得分:6)

(以下原始答案)

好的,通过您的澄清更新,您将无法使用正则表达式轻松地 ,除非您也只接受-作为“从开始到结束” 。在这种情况下,您可以使用:

>>> r = re.compile('^((?P<fixed>\d+)|(?P<begin>\d+)?-(?P<end>\d+)?)$')
>>> r.match('123').groupdict()
{'begin': None, 'fixed': '123', 'end': None}
>>> r.match('123-').groupdict()
{'begin': '123', 'fixed': None, 'end': None}
>>> r.match('-456').groupdict()
{'begin': None, 'fixed': None, 'end': '456'}
>>> r.match('-').groupdict()
{'begin': None, 'fixed': None, 'end': None}
>>> r.match('123-456').groupdict()
{'begin': '123', 'fixed': None, 'end': '456'}

否则,您将不得不使用更明确的案例:

>>> r = re.compile('^((?P<fixed>\d+)|-(?P<endonly>\d+)|(?P<begin>\d+)(?:-(?P<end>\d+)?))$')
>>> r.match('123').groupdict()
{'endonly': None, 'begin': None, 'fixed': '123', 'end': None}
>>> r.match('123-').groupdict()
{'endonly': None, 'begin': '123', 'fixed': None, 'end': None}
>>> r.match('-456').groupdict()
{'endonly': '456', 'begin': None, 'fixed': None, 'end': None}
>>> r.match('123-456').groupdict()
{'endonly': None, 'begin': '123', 'fixed': None, 'end': '456'}
>>> r.match('-') is None
True

正如您所看到的,之后正确解释结果所需的工作量会增加复杂性。

但是,我建议你不要使用正则表达式,并直接在Python中进行解析。例如:

def parseRange (expr):
    if not expr or not re.match('^\d*-?\d*$', expr):
        raise ValueError('Not a correct range')

    # single number
    if '-' not in expr:
        return int(expr), int(expr)
    else:
        begin, end = expr.split('-')
        begin = float('-inf') if begin == '' else int(begin)
        end = float('inf') if end == '' else int(end)
        return begin, end

像这样使用:

>>> parseRange('123')
(123, 123)
>>> parseRange('123-')
(123, inf)
>>> parseRange('-456')
(-inf, 456)
>>> parseRange('123-456')
(123, 456)

原始答案

你可以把它们中的一些捕获组简单地删除:

^(?P<begin>\d+)-(?P<end>\d+)$

这也解决了-可能没有第二个数字的问题,理论上(实际上)开始和结束可能没有任何区别。

如果您不总是希望匹配范围但又想要允许单个数字,则可以将范围部分放在非捕获组中。这样你就不会得到其他组:

^(?P<begin>\d+)(?:-(?P<end>\d+))?$

最后,如果你想接受to作为分隔符,你甚至可以这样做:

^(?P<begin>\d+)(?:(?:-|\s+to\s+)(?P<end>\d+))?$
>>> r = re.compile('^(?P<begin>\d+)(?:-(?P<end>\d+))?$')
>>> r.match('123').groupdict()
{'begin': '123', 'end': None}
>>> r.match('123-456').groupdict()
{'begin': '123', 'end': '456'}

>>> r = re.compile('^(?P<begin>\d+)(?:(?:-|\s+to\s+)(?P<end>\d+))?$')
>>> r.match('123-456').groupdict()
{'begin': '123', 'end': '456'}
>>> r.match('123 to 456').groupdict()
{'begin': '123', 'end': '456'}

答案 1 :(得分:1)

要允许并正确处理您在编辑中提到的所有案例,您需要一个不同的正则表达式:

^(?=.*\d)(?P<begin>\d*)(?P<range>-?)(?P<end>\d*)$

开头的前瞻是必要的,以断言字符串中至少有一个数字(因为beginend可以是可选的,但不是两者都可以。)

作为一个详细的正则表达式解释:

^              # Start of string
(?=.*\d)       # Assert that there is at least one digit somewhere
(?P<begin>\d*) # Match 0 or more digits --> Begin
(?P<range>-?)  # Match 0 or 1 dash
(?P<end>\d*)   # Match 0 or more digits --> End
$              # End of string

live on regex101.com