查找与给定正则表达式不匹配的字符

3

我正在编写一个程序来验证并纠正给定的日期字符串。让我们以04121987作为格式为ddmmyyyy的日期。这种日期的正则表达式:

(0[1-9]|[12][0-9]|3[01])(0[1-9]|1[012])(19\d\d|20\d\d)

如果我使用正则表达式匹配字符串,在Python中它可以很好地工作。

>>> regex = re.compile(r'(0[1-9]|[12][0-9]|3[01])(0[1-9]|1[012])(19\d\d|20\d\d)')
>>> regex.findall('04121987')
[('04', '12', '1987')]

如果我有一个字符串04721987,可以清楚地看到72不是有效的月份,因此该字符串将不匹配正则表达式。
>>> regex.findall('04721987')
[]

我想找出导致正则表达式失败的字符及其位置。在这种情况下,它是7。我在Python中该如何实现?

考虑月份 13。单独一个字符都不会使正则表达式失效,而是两个字符的组合导致了失效。 - Janne Karila
月份的正则表达式是(0[1-9]|1[012]),但它并不起作用。 - satran
1
是的,但我们应该归咎于“1”还是“3”导致失败?对于其他一些正则表达式来说,这个问题可能更加复杂,因此我不惊讶正则表达式引擎中没有构建此类报告功能。 - Janne Karila
哦,是的,你说得对。我没有那样看过它。 - satran
4个回答

2
一种可能的方法是构建一个正则表达式,可以匹配任何内容,但将好的匹配和坏的匹配放在不同的组中。检查结果中哪些组被填充,以确定哪个组失败了。
>>> regex = re.compile(r'(?:(0[1-9]|[12][0-9]|3[01])|(.{,2}))(?:(0[1-9]|1[012])|(.{,2}))(?:(19\d\d|20\d\d)|(.{,4}))')
>>> regex.match('04121987').groups()
('04', None, '12', None, '1987', None)
>>> regex.match('04721987').groups()
('04', None, None, '72', '1987', None)
>>> regex.match('0412').groups()
('04', None, '12', None, None, '')

另一种方法是以适当的有效字符串为基础,逐个字符地替换为输入字符串,并在每次迭代时验证。这里我使用 datetime.datetime.strptime 进行验证。您也可以使用正则表达式,但必须接受年份高达2999年,因此问题中的那个不起作用。
from datetime import datetime

def str_to_date(s):
    good_date = '01011999'
    for i in xrange(len(good_date)):
        try:
            d = datetime.strptime(s[:i+1] + good_date[i+1:], '%d%m%Y')
        except ValueError:
            raise ValueError("Bad character '%s' at index %d" % (s[i:i+1], i))
    return d

1

这个解决方案很复杂,希望你能找到更好的方法。这段代码经过轻微测试,可能已经足够。errorindex()函数接收一个日期字符串并返回错误条目的索引列表。但是,如果第一个月份数字不正确,则存在歧义。在不知道第一个数字的情况下,无法确定第二个数字是否正确。以下是代码。注意:我忘记了闰年!

def errorindex(s):
  err = []
  for i in range(len(s)):
    if i == 0:  #month1
      if int(s[i]) < 0 or int(s[i]) > 1:
        err.append(i)
    if i == 1:  #month2
      if int(s[i-1]) == 0:
        if int(s[i]) < 1 or int(s[i]) > 9:
          err.append(i)
      elif int(s[i-1]) == 1:
        if int(s[i]) < 0 or int(s[i]) > 2:
          err.append(i)
      else:
        if int(s[i]) < 0 or int(s[i]) > 2:
          err.append(i)
    if i == 2:  #day1
      if int(s[i]) < 0 or int(s[i]) > 3:
        err.append(i)
    if i == 3:  #day2
      if int(s[i-1]) in [0,1,2] and str(s[:2]) != '02':
        if int(s[i]) < 0 or int(s[i]) > 9:
          err.append(i)
      elif int(s[i-1]) in [0,1,2] and str(s[:2]) == '02':
        if int(s[i]) < 0 or int(s[i]) > 8:
          err.append(i)
    if i == 4:  #year1
      if int(s[i]) < 1 or int(s[i]) > 2:
        err.append(i)
    if i == 5:  #year2
      if int(s[i-1]) == 1:
        if int(s[i]) != 9:
          err.append(i)  
      elif int(s[i-1]) == 2:
        if int(s[i]) != 0:
          err.append(i)
    if i ==6:
      if int(s[i]) < 0 or int(s[i]) > 9:
        err.append(i)
    if i ==7:
      if int(s[i]) < 0 or int(s[i]) > 9:
        err.append(i)
  return err

s = '04721987'  

print(errorindex(s))

我没有考虑到30天或更多天的月份的正确值。我建议使用datetime模块。您可以有一个已知的日期并将一部分替换为您的字符串以验证日期是否在范围内。这比只有一个已知日期要复杂一些,但这是一个开始。 - Octipi

1
我认为您想要的是不可能的,因为_sre模块是用C实现的。您可以尝试使用this package(通过猴子补丁sre_compile,修改路径并首先导入新的_sre等),但我认为这不值得。它是完全用Python编写的_sre包的实现,因此您将能够查看源代码、编辑它并在下一个字符不匹配时进行一些操作。您可以通过以下方式之一来执行类似的操作:
  • 将日期字符串拆分为3个部分(日、月和年)并独立匹配正则表达式
  • 使用另一种不涉及正则表达式的方式验证日期时间
也许您无法获得错误所在的确切数字,但在这种情况下,只要告诉用户哪里出错了(日、月或年),我认为这没有太大意义。

0

对我来说,最明显的答案是使用一些使用有限自动机的正则表达式库或编写自己的正则表达式库。然后,您可以通过一些修改精确确定失败的位置。但我假设这不是您想要做的。

否则,如果您知道输入将具有确切的大小和确切的日期格式,则可以将其分为3个部分 - dd mm yyyy,然后尝试分别应用每个单独字符的正则表达式。这不是很好的解决方案,但您将获得所需的结果。


我曾考虑编写一个自动化程序,但在思考是否有更简单的解决方案 :) - satran
嗯,我明白了...但我认为没有比构建自己的自动机更好、更清晰的解决方案了。你可以使用数组来实现它,因为输入非常有限,所以编写起来不会花费太长时间。而且,几个月前我还从正则表达式中编写了自己的自动化生成器,如果你感兴趣,我可以为你生成自动机表。 - Jendas
我很感激。看别人的实现总是更好的。 - satran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接