【问题标题】:What's a more efficient way of looping with regex?使用正则表达式循环的更有效方法是什么?
【发布时间】:2012-12-17 13:57:41
【问题描述】:

我有一个名称列表,用于从目标字符串列表中提取。例如:

names = ['Chris', 'Jack', 'Kim']
target = ['Chris Smith', 'I hijacked this thread', 'Kim','Christmas is here', 'CHRIS']

output = ['Chris Smith', 'Kim', 'CHRIS']

所以到目前为止的规则是:

  • 不区分大小写
  • 无法匹配部分单词('ie Christmas/hijacked 不应该匹配 Chris/Jack)
  • 字符串中的其他单词都可以,只要根据上述条件在字符串中找到名称即可。

为此,另一位 SO 用户在 this thread 中建议了此代码:

[targ for targ in target_list if any(re.search(r'\b{}\b'.format(name), targ, re.I) for name in first_names)]

到目前为止,这非常准确,但是考虑到名称列表的长度约为 5,000,目标列表的长度范围为 20-100 行,其中一些字符串最长为 30 个字符,因此运行速度非常慢。

关于如何提高性能的任何建议?

解决方案:两个基于正则表达式的解决方案都遭受了溢出错误,所以很遗憾我无法测试它们。有效的解决方案(来自@mglison 的回答)是:

new_names = set(name.lower() for name in names)
[ t for t in target if any(map(new_names.__contains__,t.lower().split())) ]

这将性能从 15 秒提高到不到 1 秒巨大

【问题讨论】:

  • 你的名字都是单字吗?
  • 呃-哦。 @DSM 即将推出 str.split + set 解决方案或类似的解决方案。 (可能用timeit 显示它可以有多快;-)
  • @mgilson:伙计,我需要一些新技巧。每个人都喜欢我的旧人..
  • @DSM -- 他们是好把戏。很难想象你能想出更好的(尽管,如果你确实想出了更好的,请发布它们,这样我就可以开始在我的答案中使用它们并继续获得支持,而无需实际开发任何类型个人创造力:-p)

标签: python regex list


【解决方案1】:

似乎您可以将它们全部组合成 1 个超级正则表达式:

import re

names = ['Chris', 'Jack', 'Kim']
target = ['Chris Smith', 'I hijacked this thread', 'Kim','Christmas is here', 'CHRIS']

regex_string = '|'.join(r"(?:\b"+re.escape(x)+r"\b)" for x in names)
print regex_string
regex = re.compile(regex_string,re.I)
print [t for t in target if regex.search(t)]

仅当名称为单个单词(无空格)时才有效的非正则表达式解决方案:

new_names = set(name.lower() for name in names)
[ t for t in target if any(map(new_names.__contains__,t.lower().split())) ]

any 表达式也可以写成:

any(x in new_names for x in t.lower().split())

any(x.lower() in new_names for x in t.split())

或者,另一个依赖 set.intersection 的变体(由下面的 @DSM 建议):

[ t for t in target if new_names.intersection(t.lower().split()) ]

如果性能真的很关键,您可以分析以查看哪个性能最好,否则选择您认为最容易阅读/理解的一个

*如果你使用的是 python2.x,你可能想要使用itertools.imap 而不是map 如果你走上面的那条路线让它懒惰地评估——这也让我想知道是否python 提供了一个惰性str.split,它的性能与非惰性版本相当......

【讨论】:

  • 而不是我使用的any(更改变量名)if new_names.intersection(t.lower().split())。我不知道使用内置 intersection 的收益是否超过了在找到第一个匹配项后需要继续搜索的损失。我确实发现非正则表达式比正则表达式版本更好地扩展到更大的名称集,但正如他们所说,所有基准都是谎言..
  • 是的,我也想过.intersection。我不确定在橡胶与道路相遇的地方哪个会表现得更好。 intersection 应该很快,但你不能短路——但你也不需要创建一个可能很慢的生成器。绝对是现实世界分析员的工作。我已将您的建议添加为另一个变体,因此如果需要,OP 也可以选择对其进行分析。谢谢你。
【解决方案2】:

这是我能想到的最简单的一个:

[item for item in target if re.search(r'\b(%s)\b' % '|'.join(names), item)]

大家一起:

import re

names = ['Chris', 'Jack', 'Kim']
target = ['Chris Smith', 'I hijacked this thread', 'Kim','Christmas is here', 'CHRIS']

results = [item for item in target if re.search(r'\b(%s)\b' % '|'.join(names), item)]

print results
>>> 
['Chris Smith', 'Kim']

为了提高效率,你可以先编译正则表达式。

regex = re.compile( r'\b(%s)\b' % '|'.join(names) )
[item for item in target if regex.search(item)]

编辑

在考虑了这个问题并查看了一些 cmets 之后,我将“解决方案”修改为以下内容:

import re
names = ['Chris', 'Jack', 'Kim']
target = ['Chris Smith', 'I hijacked this thread', 'Kim','Christmas is here', 'CHRIS']
regex = re.compile( r'\b((%s))\b' % ')|('.join([re.escape(name) for name in names]), re.I )
results = [item for item in target if regex.search(item)]

结果:

>>> 
['Chris Smith', 'Kim', 'CHRIS']

【讨论】:

  • 如果您追求速度,可以提前re.compile。然后你只构建一次字符串
  • re.compile 将获得最小的好处:引擎已经缓存了大约 30 个正则表达式,但是将模式的创建提升到循环之外似乎是值得的。您可能还想做'|'.join(re.escape(names)) 以避免讨厌的副作用,以免任何名称碰巧有特殊字符,例如其中一个字符串可能是'Mr. Rose',没有转义也将匹配'Mrs Rose'
  • @Duncan -- 转义的要点。 OP 说尽管有大约 5000 个名称,所以你肯定需要编译,因为这几乎肯定会溢出各种 python 发行版中re 的任何实现的正则表达式缓存。
  • @mgilson,上面的解决方案只生成一个(长)正则表达式,所以它应该缓存就好了。
  • 在实时数据上尝试此操作时出现“OverFlowError:超出正则表达式代码大小限制”
【解决方案3】:

您当前正在另一个循环中执行一个循环,遍历两个列表。这总是会给你二次性能。

一个本地优化是编译每个名称正则表达式(这将使应用每个正则表达式更快)。但是,最大的胜利是将所有正则表达式组合成一个正则表达式,然后将其应用于输入中的每个项目。请参阅@mgilson 的答案以了解如何做到这一点。之后,您的代码性能应该线性扩展为 O(M+N),而不是 O(M*N)。

【讨论】:

  • 我没有投反对票,但编译每个正则表达式不太可能成为问题,除非 OP 有 很多 个名称。 re 模块缓存前 100 个左右的正则表达式以提高效率(存储在字典中,因此对于典型情况,查找应该是 O(1))。
  • 我理解您对投机取巧的挫败感,但在这种情况下,我想我可能会理解为什么,您的回答虽然内容丰富,但实际上并没有通过给出实际解决方案来回答问题,有些人可能会觉得没有效率,我个人认为,如果“答案”也可以作为指南并支持实际问题,那就太好了,尽管不幸的是,如今人们似乎将“答案”理解为“预先构建的解决方案” .我不会把它放在心上。
  • @mgilson (a) 他有 5000 个名字 (b) 正如我明确指出的,这是一种局部优化,不会让他脱离 M*N 领域。
  • @InbarRose 我认为复制您和 mgilson 的代码没有意义。相反,我提供了为什么您的方法有效的原因(在撰写本文时,两个答案都没有)。
猜你喜欢
  • 2015-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-15
  • 1970-01-01
  • 2015-06-09
相关资源
最近更新 更多