【问题标题】:Efficiently String searching in Python在 Python 中高效地进行字符串搜索
【发布时间】:2016-04-19 05:28:18
【问题描述】:

假设我有一个包含大约 2,000 个关键字的数据库,每个关键字都映射到几个常见的变体

例如:

 "Node" : ["node.js", "nodejs", "node js", "node"] 

 "Ruby on Rails" : ["RoR", "Rails", "Ruby on Rails"]

我想搜索一个字符串(好的,一个文档)并返回一个包含所有关键字的列表。

我知道我可以遍历大量 regex 搜索,但是有没有更有效的方法呢?对于网络应用程序来说,是近似“实时”还是接近实时的?

我目前正在查看Elastic Search 文档,但我想知道是否有Pythonic 方法来实现我的结果。

我对@9​​87654325@ 很熟悉,但我现在不想写这么多正则表达式。如果您能指出正确的方向,我将不胜感激。

【问题讨论】:

  • Elasticsearch 确实是要走的路!
  • 您可以分享任何可以让我快速入门的好文档或链接吗?
  • elasticsearch 甚至可以从 10000(2000 X ~5 个同义词)词条输入中创建一组匹配词吗?仍然 - 需要使用下面的倒排字典思想来将完整 10000 个单词上的搜索器马赫过滤回关键的 2000 个术语。

标签: python regex algorithm elasticsearch tags


【解决方案1】:

您可以使用一种数据结构来反转这个关键字字典 - 这样每个["node.js", "nodejs", "node js", "node", "Node"] 都是一个值为“节点”的键 - 其他 2000 个关键字的 10 个左右的变体中的每一个都指向一个关键字 - 所以一个 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

至于效率,这种方法相当不错,因为文本中的每个单词只会在字典中查找一次,而 Python 的 dict 搜索是 O(log(n)) - 这会给你一个 O( n log(n)) 方法。无论正则表达式匹配有多快(与 dict 查找相比,它并没有那么快),尝试您所想的单个大型正则表达式都将是 O(n²)。

如果文本太长,可能用简单的拆分(或正则表达式)对其进行预标记是不可行的 - 在这种情况下,您可以每次只读取一段文本,然后将其中的一小段分割成单词.

其他方法

由于您不需要计算每个单词的数量,因此另一种方法是使用文档中的单词和列表中的所有关键字创建 Python 集,然后取这两个集的交集。您只能针对上面的 words 倒排字典计算此交集的关键字。

赶上 这些都没有考虑包含空格的术语 - 我一直在考虑可以将单词标记为单独匹配,但是 str.split 和简单的标点删除正则表达式不能解释组合术语,如“ruby on rails”和“节点js'。如果您没有其他解决方法,那么您将不得不编写一个自定义标记器,而不是“拆分”,它可以尝试将整个文本中的一个、两个和三个单词的集合与倒置的 dict 匹配。

【讨论】:

  • 不错的答案,但效率不高!我的文本是一个大文档,那么它需要很长时间!
  • 我评论了上面的效率。请注意,您急于认为正则表达式会神奇地更快:它们在本机代码中运行,但它们并不神奇。
  • @python:当人们努力帮助你时,表面上猜测一种方法是否有效有点不合理:至少测试并说出“使用这种方法,我每秒只管理 X 个单词,但考虑到我的实际文档大小,我需要每秒 Y 个单词才能为用户提供合理的体验”。在准备这种建设性的意见时,您可能偶尔会发现某些东西是足够有效的,尽管您有直觉,如果不是至少这个人知道他们为什么要寻找替代方案以及他们有多接近......
  • 我很抱歉!这是一个 Web 应用程序,我需要编写大量代码来测试此功能。但我要感谢你,给了我一个好的开始!
【解决方案2】:

对长字符串进行标记的另一种方法是构造一个综合正则表达式,然后使用命名组来识别标记。它需要一些设置,但识别阶段被推入 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”字符。

【讨论】:

  • 这是一个极好的解释。谢谢 :) 希望我能给你更多的赞许!
猜你喜欢
  • 1970-01-01
  • 2014-05-08
  • 1970-01-01
  • 2020-06-01
  • 2019-05-29
  • 2012-08-28
  • 1970-01-01
  • 1970-01-01
  • 2018-05-22
相关资源
最近更新 更多