2012-11-22 19 views
1

我想从字符串中解析出所有日期(可能以不同的形式写入)。问题是可能有一个日期写在这种形式d/m -y例如22/11 -12。但是也可能有一个日期用这种形式d/m写成,没有指定年份。如果我在这个包含更长形式的字符串中找到日期,我不希望它以更短的形式再次找到。这是我的代码失败的地方,它找到了第一个日期两次(一年与一年,一次没有它)。确保两个正则表达式找不到相同的结果

我真的有两个问题:(1)做这件事的“正确”方法是什么?看来我真的从错误的角度来解决这个问题。 (2)如果我应该坚持这样做的话,那么这条线datestring.replace(match.group(0), '')怎么会不删除日期以至于我再也找不到了?

这是我的代码:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

import re 

dformats = (
    '(?P<day>\d{1,2})/(?P<month>\d{1,2}) -(?P<year>\d{2})', 
    '(?P<day>\d{1,2})/(?P<month>\d{1,2})', 
    '(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', 
      ) 


def get_dates(datestring): 
    """Try to extract all dates from certain strings. 

    Arguments: 
    - `datestring`: A string containing dates. 
    """ 
    global dformats 

    found_dates = [] 

    for regex in dformats: 
     matches = re.finditer(regex, datestring) 
     for match in matches: 
      # Is supposed to make sure the same date is not found twice 
      datestring.replace(match.group(0), '') 

      found_dates.append(match) 
    return found_dates 

if __name__ == '__main__': 
    dates = get_dates('1/2 -13, 5/3 & 2012-11-22') 
    for date in dates: 
     print date.groups() 

回答

2

两种方式:

  1. 使用一个正则表达式,并使用|运营商所有的情况下,联合起来:

    expr = re.compile (r"expr1|expr2|expr3")

  2. 仅查找单个实例,然后通过下一个搜索“开始位置”。请注意,这会使事情变得复杂,因为无论选择哪种格式,您都希望始终以最早的匹配开始。也就是说,在所有三场比赛中循环,找出最早的比赛,做替换,然后用增加的起始位置重新做。这使得选项1更容易。

几个附加分:

  1. 确保你使用的“原始字符串”:前面加上在每个字符串的前一个“R”。否则,'\'字符冒险被吃掉并且不会传递到RE引擎

  2. 考虑使用“sub”和回调函数代替“repl”参数来执行替换,而不是finditer。在这种情况下,“repl”传递一个匹配对象,并且应该返回替换字符串。

  3. 如果没有选择该替代选项,则在“单个”re中的匹配组将具有值None,因此可以轻松检测到使用了哪个替代方案。

  4. 除非您打算修改该变量,否则不应该说“全局”。

这是一些完整的工作代码。

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

import re 

expr = re.compile(
    r'(?P<day1>\d{1,2})/(?P<month1>\d{1,2}) -(?P<year>\d{2})|(?P<day2>\d{1,2})/(?P<month2>\d{1,2})|(?P<year3>\d{4})-(?P<month3>\d{2})-(?P<day3>\d{2})') 


def get_dates(datestring): 
    """Try to extract all dates from certain strings. 

    Arguments: 
    - `datestring`: A string containing dates. 
    """ 

    found_dates = [] 
    matches = expr.finditer(datestring) 
    for match in matches: 
     if match.group('day1'): 
      found_dates.append({'day': match.group('day1'), 
           'month': match.group('month1') }) 
     elif match.group('day2'): 
      found_dates.append({'day': match.group('day2'), 
           'month': match.group('month2')}) 
     elif match.group('day3'): 
      found_dates.append({'day': match.group('day3'), 
           'month': match.group('month3'), 
           'year': match.group('year3')}) 
     else: 
      raise Exception("wtf?") 
    return found_dates 

if __name__ == '__main__': 
    dates = get_dates('1/2 -13, 5/3 & 2012-11-22') 
    for date in dates: 
     print date 
+0

很好的回答,恭喜! – georg

+1

Emacs抱怨你不遵循pep-8 ;-)否则一个很好的答案。尽管在正则表达式中使用'|'这不是处理相同名称组的简单方法,但我困扰了我。有6行处理这个问题(!): -/ –

+0

Niclas:是的,我编辑了它大约3次,可能仍然没有正确的答案:)另外,是的,我很沮丧,RE引擎didn'让我重新使用这些名称,我可以创建一个简单的函数来重新映射以清理代码。如果我们可以假设我们总是将PERIOD-X拷贝到PERIOD,如果dayX存在,X在范围内(1,4),PERIOD在['day','month','year'],那么这只是一个几行。任何更复杂的处理可能都必须像上面那样分解它。 –

2

您可以使用negative look ahead在你的第二个正则表达式来这不跟-year只匹配那些dates: -

dformats = (
    r'(?P<day>\d{1,2})/(?P<month>\d{1,2}) -(?P<year>\d{2})', 
    r'(?P<day>\d{1,2})/(?P<month>\d{1,2})(?!\s+-(?P<year>\d{2}))', 
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})' 
) 

因此,这在first正则表达式匹配的日期,也不会在第二个匹配。

1

您可以sub而不是find

def find_dates(s): 

    dformats = (
     '(?P<day>\d{1,2})/(?P<month>\d{1,2}) -(?P<year>\d{2})', 
     '(?P<day>\d{1,2})/(?P<month>\d{1,2})', 
     '(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', 
    )  

    dates = [] 
    for f in dformats: 
     s = re.sub(f, lambda m: dates.append(m.groupdict()), s) 
    return dates 
相关问题