可以简化这些Python正则表达式吗?

时间:2011-08-09 18:20:48

标签: python regex

    patterns = {}
    patterns[1] = re.compile("[A-Z]\d-[A-Z]\d")
    patterns[2] = re.compile("[A-Z]\d-[A-Z]\d\d")
    patterns[3] = re.compile("[A-Z]\d\d-[A-Z]\d\d")
    patterns[4] = re.compile("[A-Z]\d\d-[A-Z]\d\d\d")
    patterns[5] = re.compile("[A-Z]\d\d\d-[A-Z]\d\d\d")
    patterns[6] = re.compile("[A-Z][A-Z]\d-[A-Z][A-Z]\d")
    patterns[7] = re.compile("[A-Z][A-Z]\d-[A-Z][A-Z]\d\d")
    patterns[8] = re.compile("[A-Z][A-Z]\d\d-[A-Z][A-Z]\d\d")
    patterns[9] = re.compile("[A-Z][A-Z]\d\d-[A-Z][A-Z]\d\d\d")
    patterns[10] = re.compile("[A-Z][A-Z]\d\d\d-[A-Z][A-Z]\d\d\d")

    def matchFound(toSearch):
        for items in sorted(patterns.keys(), reverse=True):
            matchObject = patterns[items].search(toSearch)
            if matchObject:
                return items
        return 0

然后我使用以下代码来查找匹配项:

        while matchFound(toSearch) > 0:

我有10种不同的正则表达式,但我觉得它们可以被一个写得很好,更优雅的正则表达式所取代。你们认为这有可能吗?

编辑:忘记两次表达:

  patterns[11] = re.compile("[A-Z]\d-[A-Z]\d\d\d")
  patterns[12] = re.compile("[A-Z][A-Z]\d-[A-Z][A-Z]\d\d\d")

EDIT2:我最终得到了以下内容。我意识到我可以获得额外的结果,但我不认为它们在我正在解析的数据中是可能的。

    patterns = {}
    patterns[1] = re.compile("[A-Z]{1,2}\d-[A-Z]{1,2}\d{1,3}")
    patterns[2] = re.compile("[A-Z]{1,2}\d\d-[A-Z]{1,2}\d{2,3}")
    patterns[3] = re.compile("[A-Z]{1,2}\d\d\d-[A-Z]{1,2}\d\d\d")

4 个答案:

答案 0 :(得分:4)

Sean Bright为您提供了所需的答案。这里只是一个一般提示:

Python有很棒的文档。在这种情况下,您可以使用“help”命令阅读它:

import re
help(re)

如果你阅读了帮助,你会看到:

{m,n}    Matches from m to n repetitions of the preceding RE.

使用Google也很有帮助。 “Python正则表达式”为我找到了这些链接:

http://docs.python.org/library/re.html

http://docs.python.org/howto/regex.html

两者都值得一读。

答案 1 :(得分:4)

Josh Caswell指出,Sean Bright的答案将比原始组更多地输入。对不起,我没想到这个。 (将来再详细说明你的问题可能会更好。)

所以你的基本问题是正则表达式无法计算。但是我们仍然可以用非常灵巧的方式在Python中解决这个问题。首先,我们制作一个与您的任何合法输入相匹配的模式,但也会匹配您想要拒绝的模式。

接下来,我们定义一个使用该模式的函数,然后检查匹配对象,并计算以确保匹配的字符串符合长度要求。

import re
_s_pat = r'([A-Z]{1,2})(\d{1,3})-([A-Z]{1,2})(\d{1,3})'
_pat = re.compile(_s_pat)

_valid_n_len = set([(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)])
def check_match(s):
    m = _pat.search(s)
    try:
        a0, n0, a1, n1 = m.groups()
        if len(a0) != len(a1):
            return False
        if not (len(n0), len(n1)) in _valid_n_len:
            return False
        return True
    except (AttributeError, TypeError, ValueError):
        return False

以上是对上述代码的一些解释。

首先我们使用原始字符串来定义模式,然后我们预编译模式。我们可以将文字字符串填充到re.compile()的调用中,但我喜欢有一个单独的字符串。我们的模式有四个不同的部分用括号括起来;这些将成为“匹配组”。有两个匹配组匹配字母字符,两个匹配组匹配数字。这个模式将匹配您想要的所有内容,但不会排除您不想要的内容。

接下来,我们声明一个set,它具有数字的所有有效长度。例如,第一组数字可以是1位数字,第二组可以是2位数字;这是(1,2)tuple值)。集合是指定我们想要合法的所有可能组合的好方法,同时仍然能够快速检查给定的一对长度是否合法。

函数check_match()首先使用模式匹配字符串,返回绑定到名称m的“匹配对象”。如果搜索失败,m可能会设置为None。我没有明确测试None,而是使用try / except块;回想起来,测试None可能会更好。对不起,我不是故意混淆。但是try / except块是一种非常简单的方法来包装并使其非常可靠,所以我经常将它用于这样的事情。

最后,check_match()将匹配组解包为四个变量。两个alpha组是a0和a1,两个数字组是n0和n1。然后它检查长度是否合法。据我所知,规则是alpha组需要相同的长度;然后我们构建tuple个数字组长度,并检查tuple是否在我们set的有效tuple中。

以上是略有不同的版本。也许你会更喜欢它。

import re
# match alpha: 1 or 2 capital letters
_s_pat_a = r'[A-Z]{1,2}'
# match number: 1-3 digits
_s_pat_n = r'\d{1,3}'

# pattern: four match groups: alpha, number, alpha, number
_s_pat = '(%s)(%s)-(%s)(%s)' % (_s_pat_a, _s_pat_n, _s_pat_a, _s_pat_n)
_pat = re.compile(_s_pat)

# set of valid lengths of number groups
_valid_n_len = set([(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)])

def check_match(s):
    m = _pat.search(s)
    if not m:
        return False
    a0, n0, a1, n1 = m.groups()
    if len(a0) != len(a1):
        return False
    tup = (len(n0), len(n1)) # make tuple of actual lengths
    if not tup in _valid_n_len:
        return False
    return True

注意:看起来有效长度的规则实际上很简单:

    if len(n0) > len(n1):
        return False

如果该规则适合您,您可以摆脱集合和元组的东西。嗯,我会把变量名称缩短一点。

import re
# match alpha: 1 or 2 capital letters
pa = r'[A-Z]{1,2}'
# match number: 1-3 digits
pn = r'\d{1,3}'

# pattern: four match groups: alpha, number, alpha, number
p = '(%s)(%s)-(%s)(%s)' % (pa, pn, pa, pn)
_pat = re.compile(p)

def check_match(s):
    m = _pat.search(s)
    if not m:
        return False
    a0, n0, a1, n1 = m.groups()
    if len(a0) != len(a1):
        return False
    if len(n0) > len(n1):
        return False
    return True

答案 2 :(得分:3)

Josh至少可以减少RE的数量。

但是你也可以选择比允许的更宽的RE,然后另外检查是否满足所有条件。如

pattern = re.compile("([A-Z]{1,2})(\d{1,3})-([A-Z]{1,2})(\d{1,3})")

然后

matchObject = pattern.search(toSearch)
if matchObject and <do something with the length of the groups, comparing them)>:
    return <stuff>

但即使由于任何原因而无效,也有办法改善:

patterns = tuple(re.compile(r) for r in (
    "[A-Z]\d-[A-Z]\d{1,2}",
    "[A-Z]\d\d-[A-Z]\d{2,3}",
    "[A-Z]\d\d\d-[A-Z]\d\d\d",
    "[A-Z][A-Z]\d-[A-Z][A-Z]\d{1,2}",
    "[A-Z][A-Z]\d\d-[A-Z][A-Z]\d{2,3}",
    "[A-Z][A-Z]\d\d\d-[A-Z][A-Z]\d\d\d",
)

def matchFound(toSearch):
    for pat in reversed(patterns):
        matchObject = pat.search(toSearch)
        if matchObject:
            return items # maybe more useful?
    return None

答案 3 :(得分:1)

以Sean(现在显然已删除)的答案为基础,您可以减少模式的数量。由于数字匹配长度组合的限制(即,如果m在第一个位置,至少m并且在第二个位置不超过3)我不确定你能得到它归结为一个:

"[A-Z]\d-[A-Z]\d{1,3}"
"[A-Z]\d\d-[A-Z]\d{2,3}"
"[A-Z]\d\d\d-[A-Z]\d\d\d"
"[A-Z][A-Z]\d-[A-Z][A-Z]\d{1,3}"
"[A-Z][A-Z]\d\d-[A-Z][A-Z]\d{2,3}"
"[A-Z][A-Z]\d\d\d-[A-Z][A-Z]\d\d\d"

这使用{m,n} repeat qualifier syntax,它指定前一个匹配至少重复m但不超过n次。您还可以指定一个数字n;然后匹配必须完全n次成功:

"[A-Z]{2}\d-[A-Z]{2}\d{2,3}"