在 Python 中有效地搜索字符串
Efficiently String searching in Python
假设我有一个包含大约 2,000 个关键字的数据库,每个关键字都映射到一些常见的变体
例如:
"Node" : ["node.js", "nodejs", "node js", "node"]
"Ruby on Rails" : ["RoR", "Rails", "Ruby on Rails"]
我想搜索一个字符串(好的,一个文档)和return一个包含所有关键字的列表。
我知道我可以遍历大量 regex
搜索,但是有没有更有效的方法呢?网络应用程序接近 "real time" 或接近实时?
我目前正在查看 Elastic Search 文档,但我想知道是否有 Pythonic
方法可以实现我的结果。
我对regex
很熟悉,但我现在不想写那么多正则表达式。如果您能给我指明正确的方向,我将不胜感激。
您可以使用 data-structure 来反转这个关键字字典 - 这样每个 ["node.js", "nodejs", "node js", "node", "Node"]
都是一个值为 "Node" 的键 - 大约 10 个变体中的每个变体对于其他 2000 个关键字,指向其中一个关键字 - 所以一个 20000 大小的字典,这并不多。
使用 taht dict,您可以将您的文本重新标记为仅由关键字的规范化形式组成,然后它们继续计数。
primary_dict = {
"Node" : ["node.js", "nodejs", "node js", "node", "Node"]
"Ruby_on_Rails" : ["RoR", "Rails", "Ruby on Rails"]
}
def invert_dict(src):
dst = {}
for key, values in src.items():
for value in values:
dst[value] = key
return dst
words = invert_dict(primary_dict)
from collections import Counter
def count_keywords(text):
counted = Counter()
for word in text.split(): # or use a regex to split on punctuation signs as well
counted[words.get(word, None)] += 1
return counted
至于效率,这个方法还是不错的,因为文本中的每个单词只会在字典中被looked-up一次,而Python的字典搜索是O(log( n)) - 这给了你一个 O(n log(n)) 方法。尝试你所想的 single-mega-regexp 将是 O(n²),无论正则表达式匹配有多快(与 dict 查找相比它并不那么快)。
如果文本太长,也许 pre-tokenizing 用简单的分割(或正则表达式)是不可行的 - 在这种情况下,你可以每次只读一段文本,然后分成小块用文字表达。
其他方法
由于您不需要计算每个单词的数量,另一种方法是使用文档中的单词和列表中的所有关键字创建 Python 集合,然后取两个集合的交集.你可以只计算 this 交集的关键字与上面的 words
inverted dict 的对比。
抓住
None 其中考虑了包含空格的术语 - 我一直在考虑单词可以标记化以单独匹配,但是 str.split,简单的 punctuation-removing 正则表达式不能解释组合术语像 'ruby on rails' 和 'node js'。如果您没有其他解决方法,您将不得不编写一个自定义分词器,而不是 'split',它可以尝试将整个文本中的一组单词、两个单词和三个单词与倒置字典进行匹配。
另一种对长字符串标记化有用的方法是构造一个综合正则表达式,然后使用命名组来识别标记。它需要一些设置,但识别阶段被推入 C/native 代码,并且只需要一次通过,因此它可以非常有效。例如:
import re
tokens = {
'a': ['andy', 'alpha', 'apple'],
'b': ['baby']
}
def create_macro_re(tokens, flags=0):
"""
Given a dict in which keys are token names and values are lists
of strings that signify the token, return a macro re that encodes
the entire set of tokens.
"""
d = {}
for token, vals in tokens.items():
d[token] = '(?P<{}>{})'.format(token, '|'.join(vals))
combined = '|'.join(d.values())
return re.compile(combined, flags)
def find_tokens(macro_re, s):
"""
Given a macro re constructed by `create_macro_re()` and a string,
return a list of tuples giving the token name and actual string matched
against the token.
"""
found = []
for match in re.finditer(macro_re, s):
found.append([(t, v) for t, v in match.groupdict().items() if v is not None][0])
return found
最后一步,运行完成它:
macro_pat = create_macro_re(tokens, re.I)
print find_tokens(macro_pat, 'this is a string of baby apple Andy')
macro_pat
最终对应于:
re.compile(r'(?P<a>andy|alpha|apple)|(?P<b>baby)', re.IGNORECASE)
第二行打印一个元组列表,每个元组都给出标记和与标记匹配的实际字符串:
[('b', 'baby'), ('a', 'apple'), ('a', 'Andy')]
此示例展示了如何将标记列表编译成单个正则表达式,并且可以高效地 运行 一次通过一个字符串。
未显示的是它的一大优势:不仅可以通过字符串,还可以通过正则表达式定义标记。因此,如果我们想要 b
标记的替代拼写,例如,我们不必详尽地列出它们。正常的正则表达式模式就足够了。假设我们还想将 'babby' 识别为 b
标记。我们可以像以前一样做 'b': ['baby', 'babby']
,或者我们可以使用正则表达式做同样的事情:'b': ['babb?y']
。或者 'bab+y'
如果你还想包含任意内部 'b' 字符。
假设我有一个包含大约 2,000 个关键字的数据库,每个关键字都映射到一些常见的变体
例如:
"Node" : ["node.js", "nodejs", "node js", "node"]
"Ruby on Rails" : ["RoR", "Rails", "Ruby on Rails"]
我想搜索一个字符串(好的,一个文档)和return一个包含所有关键字的列表。
我知道我可以遍历大量 regex
搜索,但是有没有更有效的方法呢?网络应用程序接近 "real time" 或接近实时?
我目前正在查看 Elastic Search 文档,但我想知道是否有 Pythonic
方法可以实现我的结果。
我对regex
很熟悉,但我现在不想写那么多正则表达式。如果您能给我指明正确的方向,我将不胜感激。
您可以使用 data-structure 来反转这个关键字字典 - 这样每个 ["node.js", "nodejs", "node js", "node", "Node"]
都是一个值为 "Node" 的键 - 大约 10 个变体中的每个变体对于其他 2000 个关键字,指向其中一个关键字 - 所以一个 20000 大小的字典,这并不多。
使用 taht dict,您可以将您的文本重新标记为仅由关键字的规范化形式组成,然后它们继续计数。
primary_dict = {
"Node" : ["node.js", "nodejs", "node js", "node", "Node"]
"Ruby_on_Rails" : ["RoR", "Rails", "Ruby on Rails"]
}
def invert_dict(src):
dst = {}
for key, values in src.items():
for value in values:
dst[value] = key
return dst
words = invert_dict(primary_dict)
from collections import Counter
def count_keywords(text):
counted = Counter()
for word in text.split(): # or use a regex to split on punctuation signs as well
counted[words.get(word, None)] += 1
return counted
至于效率,这个方法还是不错的,因为文本中的每个单词只会在字典中被looked-up一次,而Python的字典搜索是O(log( n)) - 这给了你一个 O(n log(n)) 方法。尝试你所想的 single-mega-regexp 将是 O(n²),无论正则表达式匹配有多快(与 dict 查找相比它并不那么快)。
如果文本太长,也许 pre-tokenizing 用简单的分割(或正则表达式)是不可行的 - 在这种情况下,你可以每次只读一段文本,然后分成小块用文字表达。
其他方法
由于您不需要计算每个单词的数量,另一种方法是使用文档中的单词和列表中的所有关键字创建 Python 集合,然后取两个集合的交集.你可以只计算 this 交集的关键字与上面的 words
inverted dict 的对比。
抓住 None 其中考虑了包含空格的术语 - 我一直在考虑单词可以标记化以单独匹配,但是 str.split,简单的 punctuation-removing 正则表达式不能解释组合术语像 'ruby on rails' 和 'node js'。如果您没有其他解决方法,您将不得不编写一个自定义分词器,而不是 'split',它可以尝试将整个文本中的一组单词、两个单词和三个单词与倒置字典进行匹配。
另一种对长字符串标记化有用的方法是构造一个综合正则表达式,然后使用命名组来识别标记。它需要一些设置,但识别阶段被推入 C/native 代码,并且只需要一次通过,因此它可以非常有效。例如:
import re
tokens = {
'a': ['andy', 'alpha', 'apple'],
'b': ['baby']
}
def create_macro_re(tokens, flags=0):
"""
Given a dict in which keys are token names and values are lists
of strings that signify the token, return a macro re that encodes
the entire set of tokens.
"""
d = {}
for token, vals in tokens.items():
d[token] = '(?P<{}>{})'.format(token, '|'.join(vals))
combined = '|'.join(d.values())
return re.compile(combined, flags)
def find_tokens(macro_re, s):
"""
Given a macro re constructed by `create_macro_re()` and a string,
return a list of tuples giving the token name and actual string matched
against the token.
"""
found = []
for match in re.finditer(macro_re, s):
found.append([(t, v) for t, v in match.groupdict().items() if v is not None][0])
return found
最后一步,运行完成它:
macro_pat = create_macro_re(tokens, re.I)
print find_tokens(macro_pat, 'this is a string of baby apple Andy')
macro_pat
最终对应于:
re.compile(r'(?P<a>andy|alpha|apple)|(?P<b>baby)', re.IGNORECASE)
第二行打印一个元组列表,每个元组都给出标记和与标记匹配的实际字符串:
[('b', 'baby'), ('a', 'apple'), ('a', 'Andy')]
此示例展示了如何将标记列表编译成单个正则表达式,并且可以高效地 运行 一次通过一个字符串。
未显示的是它的一大优势:不仅可以通过字符串,还可以通过正则表达式定义标记。因此,如果我们想要 b
标记的替代拼写,例如,我们不必详尽地列出它们。正常的正则表达式模式就足够了。假设我们还想将 'babby' 识别为 b
标记。我们可以像以前一样做 'b': ['baby', 'babby']
,或者我们可以使用正则表达式做同样的事情:'b': ['babb?y']
。或者 'bab+y'
如果你还想包含任意内部 'b' 字符。