【问题标题】:Python list of strings and list of regexes, clean way to find strings which don't match anything?Python 字符串列表和正则表达式列表,找到不匹配的字符串的干净方法?
【发布时间】:2013-07-12 18:29:28
【问题描述】:

所以,我有一个正则表达式模式列表和一个字符串列表,我想做的是在这个字符串列表中说,是否有任何字符串不匹配任何正则表达式。

目前,我正在从两个字典中提取正则表达式,以及正则表达式要匹配的值:

我从两个字典中制作了两个列表,一个是模式,一个是键:

patterns = []
keys = []
for pattern, schema in patternproperties.items():
    patterns.append(pattern)
for key, value in value_obj.items():
    keys.append(key)

# Now work out if there are any non-matching keys

for key in keys:
    matches = 0
    for pattern in patterns:
        if re.match(pattern, key):
            matches += 1
    if matches == 0:
        print 'Key %s matches no patterns' %(key)

但这似乎非常低效。任何人都可以找到更好的解决方案吗?

【问题讨论】:

  • 一个简单的改进是在找到匹配键的正则表达式后跳出循环。
  • 您确定要使用re.match吗? search() vs. match()
  • 您的patterns 列表完全没用。只需遍历 patternproperties 字典。
  • 类似地:for pattern, schema in patternproperties.items(): patterns.append(pattern)patterns = patternproperties.keys() 做的事情完全相同,只是不那么明显,更冗长,而且启动速度可能更慢。同样适用于keys。只是value_obj.keys()。而且,正如 Bakuriu 指出的那样,遍历字典与遍历其键是一样的。

标签: python regex


【解决方案1】:

正则表达式针对搜索大块文本而不是小块序列进行了优化。因此,您可能需要考虑搜索'\n'.join(keys),而不是单独搜索每一个。

或者,或者,不是将循环从 Python 移动到正则表达式,而是将隐式“或”/“任何”位从 Python 移动到正则表达式:

pattern = re.compile('|'.join('({})'.format(p) for p in patterns))    
for key in keys:
    if not pattern.match(key):
        print 'Key %s matches no patterns' %(key)

另外,请注意我使用了re.compile。由于自动正则表达式缓存,这可能无济于事……但它永远不会受到伤害,而且它通常也使代码更易于阅读。


来自一个快速的timeit 测试,带有简短的键列表和不同数量的简单模式:

patterns   original   alternation
2          76.1 us    42.4 us
3          109 us     42.5 us
4          143 us     43.3 us

所以,我们的模式数量已经从线性变为几乎恒定。

当然,这不会支持更复杂的模式,或者太多的模式。

【讨论】:

  • 好主意。这会更快,减少从 inputCount * patternCount 到 inputCount + patternCount 的迭代次数。
  • @fatcat1111:我假设您的意思是第二种选择。这不是真的。即使在所有模式完全不同的最佳情况下,也存在 O(N log M) 项,与 O(M) 项相比,它的系数非常小。但是如果模式开始共享可能的前缀,它会在 M 上变得超线性。如果你加入后视等,它甚至可以将非指数表达式推入指数表达式。所以,它通常不会比 N+M 差太多,但实际复杂度是……嗯,我猜是无限的。
  • 感谢您的分析,非常感谢您抽出宝贵时间来解释这一点。不幸的是,我没有完全遵循(对不起)。我的想法是您需要遍历所有模式一次(您的列表理解),并遍历所有键一次(您的 for 循环),因此总复杂度为 O(n+m)。但你是说有一个对数项?是从模式本身的评估来看的吗?再次感谢您花时间解释这一点。
  • 连接模式(并编译结果)的 O(m) 非常小; match 部分(以复杂的方式)依赖于 n 和 m。如果您查看组合正则表达式产生的 NFA,它会更容易理解,但仍然不是微不足道的(除非您的交替是完全独立的简单模式)。
  • 我想我明白了。所以你的分析不仅考虑了模式的数量和输入字符串的数量,还考虑了构成编译模式的“子模式”的数量和复杂性,对吗?如果是这样,那似乎比我的幼稚分析更有意义。谢谢!
【解决方案2】:
[key for key in keys if not any(re.match(pattern, key) for pattern in patterns)]

【讨论】:

  • 你能解释一下为什么这样更有效率吗?
  • @EmilSit any 在找到匹配的模式时短路。 OP的代码没有。
  • 另外,在 listcomp 中循环比显式 Python 循环要快一点。但这是一个微优化,在您消除更大的问题之前不值得担心。
  • 我想这可以通过避免前两个列表创建中的任何一个来大量整理:[key for key in value_obj if not any(re.match(pattern, key) for pattern in patternproperties)]。感谢您的提示!
  • @Ignacio 请尝试解释代码,而不是仅仅给出答案,以便其他人可以从中学习,它会更适用。
【解决方案3】:

您可以通过多种方式对此进行优化。基本算法是合理的,所以你有一些选择:

  • 如果有匹配项,请尽早退出循环(而不是计算匹配的数量,您并不关心)。
  • 缓存正则表达式的编译(如果你have a lot of patterns)。
  • 对正则表达式进行排序,以便能够快速匹配的表达式排在第一位。这样一来,您的提前终止就可以得到最大的回报。
  • 使用列表解析,它可能比手动迭代更快(并且可能有一天允许 Python 解释器并行化,尽管今天可​​能不会)。虽然它不一定更容易阅读。 (请参阅is it better to use list comprehensions or for each loops 了解一些意见。)

一种不同的算法可能是首先迭代模式,并在一个模式匹配时立即从潜在键集中删除一些东西。比如:

remainder = set(keys)
for pattern in patterns:
    toremove = set()
    for key in remainder:
        if re.match(pattern, key):
            toremove.add(key)
    remainder -= toremove

如果您有一个匹配很多键的模式,这可能会有所帮助。

您当然应该根据您的情况和输入进行衡量,以确定哪些优化是最合适的。

【讨论】:

  • 对模式排序可能是一项非常复杂的任务(考虑模式是由用户输入还是从某个配置文件等)。 Self-organizing lists 在这里可能很有用(当一个模式匹配时,它被带到列表的顶部),但这只有在有很多模式时才重要。
  • @johnthexiii:不,它没有。 Here 是一些证据。 (使用 CPython 3.3.1。使用 PyPy 2.0b,时间为 2.26us 和 2.28us。)请参阅 PySequence_FastPySequence_Fast_GET_ITEM 了解原因,或查看源代码以获取完整详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-02
  • 2020-11-29
  • 2011-11-28
  • 2013-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多