【问题标题】:Python - Fast count words in text from list of strings and that start withPython - 从字符串列表中快速计算文本中的单词并以
【发布时间】:2021-03-16 07:14:27
【问题描述】:

我知道类似的问题已经被问过好几次了,但我的问题有点不同,我正在寻找一个省时的解决方案,用 Python。

我有一组单词,其中一些以“*”结尾,而另一些则没有:

words = set(["apple", "cat*", "dog"])

我必须计算它们在文本中的总出现次数,考虑到任何东西都可以放在星号之后(“cat*”表示所有以“cat”开头的单词)。搜索必须不区分大小写。 考虑这个例子:

text = "My cat loves apples, but I never ate an apple. My dog loves them less than my CATS".

我希望最终得分为 4(= cat* x 2 + dog + apple)。请注意,“cat*”被计算了两次,也考虑了复数,而“apple”只计算了一次,因为它的复数没有被计算(末尾没有星号)。

我必须对大量文档重复此操作,因此我需要一个快速的解决方案。我不知道 regex 或 flashtext 是否可以达到快速解决方案。你能帮帮我吗?

编辑

我忘了说我的一些词包含标点符号,例如:

words = set(["apple", "cat*", "dog", ":)", "I've"])

这似乎在编译正则表达式时会产生额外的问题。您已经提供的代码是否有一些集成可以用于这两个附加词?

【问题讨论】:

  • 感谢尼克这只是部分,但非常有用的问题。我认为答案是您和@Dani 给出的答案。我现在正试图了解哪种方法会更快。
  • 这实际上取决于您的 words 列表的大小 - 对于大型列表,Trie 解决方案可能会更快,但对于较短的情况,构建 Trie 的成本可能会超过加速的好处上正则表达式比较。知道您为您的具体示例找到了什么会很有趣。

标签: python string full-text-search re


【解决方案1】:

您可以使用正则表达式来执行此操作,从一组单词中创建一个正则表达式,将单词边界放在单词周围,但将尾随单词边界保留在以 * 结尾的单词之外。编译正则表达式应该有助于提高性能:

import re

words = set(["apple", "cat*", "dog"])
text = "My cat loves apples, but I never ate an apple. My dog loves them less than my CATS"

regex = re.compile('|'.join([r'\b' + w[:-1] if w.endswith('*') else r'\b' + w + r'\b' for w in words]), re.I)
matches = regex.findall(text)
print(len(matches))

输出:

4

【讨论】:

    【解决方案2】:

    免责声明:我是trrex的作者

    对于这个问题,如果您真的想要一个可扩展的解决方案,请使用 trie 正则表达式而不是联合正则表达式。请参阅此answer 以获得解释。一种方法是使用 trrex,例如:

    import trrex as tx
    import re
    
    words = {"apple", "cat*", "dog"}
    text = "My cat loves apples, but I never ate an apple. My dog loves them less than my CATS"
    
    prefix_set = {w.replace('*', '') for w in words if w.endswith('*')}
    full_set = {w for w in words if not w.endswith('*')}
    
    prefix_pattern = re.compile(tx.make(prefix_set, right=''), re.IGNORECASE)  # '' as we only care about prefixes
    full_pattern = re.compile(tx.make(full_set), re.IGNORECASE)
    
    res = prefix_pattern.findall(text) + full_pattern.findall(text)
    print(res)
    

    输出

    ['cat', 'CAT', 'apple', 'dog']
    

    对于 trrex 的特定用途,请参阅 this,那里描述的实验比朴素的联合正则表达式提高了 10 倍。 trie 正则表达式利用常见前缀并为以下词创建最佳正则表达式:

    ['baby', 'bat', 'bad']
    

    它会创建以下内容:

    ba(?:by|[td])
    

    【讨论】:

    • 非常感谢这个解决方案。我也非常喜欢@Nick 的那个。你的新代码会比 NIck 的更快吗?有多快?再次感谢!
    【解决方案3】:

    为您要搜索的字词创建一个Trie

    然后遍历要检查的字符串的字符。

    • 每次到达树上的一片叶子时,增加计数器并跳到下一个单词。
    • 每次没有路径时,跳到下一个单词。

    【讨论】:

    • 我真的很想看到实现这一点的代码(我怀疑 OP 也会...)
    • 当然,我有空的时候。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 2021-08-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多