【问题标题】:Python searching a large list speedPython搜索大列表速度
【发布时间】:2014-01-12 03:03:13
【问题描述】:

我在搜索一个非常大的列表时遇到了速度问题。我有一个包含很多错误和非常奇怪的单词的文件。我正在尝试使用 difflib 在包含 650,000 个单词的字典文件中查找最接近的匹配项。下面的这种方法效果很好,但速度非常慢,我想知道是否有更好的方法来解决这个问题。这是代码:

from difflib import SequenceMatcher
headWordList = [ #This is a list of 650,000 words]


openFile = open("sentences.txt","r")

for line in openFile:
    sentenceList.append[line]

percentage = 0
count = 0

for y in sentenceList:
      if y not in headwordList:

         for x in headwordList:
             m = SequenceMatcher(None, y.lower(), x)

             if m.ratio() > percentage:
                 percentage = m.ratio()

                 word = x

         if percentage > 0.86:        
             sentenceList[count] = word
count=count+1

感谢您的帮助,软件工程甚至不是我的强项。非常感谢。

【问题讨论】:

  • 我不同意。他或多或少在寻找替代方法
  • 这是一个数据结构问题。
  • 我可以立即看到的一件事是将headwordList 设为集合而不是列表,以便为in 检查获得更好的查找性能。
  • 这是基于现有的算法,还是你想一起破解一些东西?具体的0.86 让我想,如果我们知道最初的问题,我们也许可以提出一个更好的整体解决方法。
  • @EnglishGrad 对if y not in headwordList: 部分有很大帮助

标签: python list memory optimization text


【解决方案1】:

两件事可能会提供一些小帮助:

1) 使用this SO answer 中的方法最有效地读取您的大文件。

2) 更改您的代码

for x in headwordList:
    m = SequenceMatcher(None, y.lower(), 1)

到

yLower = y.lower()
for x in headwordList:
    m = SequenceMatcher(None, yLower, 1)

您将每个句子转换为低 650,000 次。没必要。

【讨论】:

    【解决方案2】:

    您应该将headwordList 更改为set。

    测试word in headwordList 会很慢。它必须对headwordList 中的每个单词进行字符串比较,一次一个单词。这将花费与列表长度成正比的时间;如果将列表的长度加倍,则进行测试所需的时间将加倍(平均)。

    使用set,执行in 测试总是需要相同的时间;它不依赖于set 中的元素数量。所以这将是一个巨大的加速。

    现在,整个循环可以简化:

         for x in headwordList:
             m = SequenceMatcher(None, y.lower(), x)
    
             if m.ratio() > percentage:
                 percentage = m.ratio()
    
                 word = x
    
         if percentage > 0.86:        
             sentenceList[count] = word
    

    所有这一切都是从headwordList中找到比率最高的单词,并保留它(但只有在比率超过0.86时才保留它)。这是一种更快的方法。我要将名称 headwordList 更改为 headwords,因为我希望您将其改为 set 而不是 list。

    def check_ratio(m):
        return m.ratio()
    
    y = y.lower()  # do the .lower() call one time
    m, word =  max((SequenceMatcher(None, y, word), word) for word in headwords, key=check_ratio)
    percentage = max(percentage, m.ratio())  # remember best ratio
    if m.ratio() > 0.86:
        setence_list.append(word)
    

    这可能看起来有点棘手,但它是 Python 中最快的方法。我们将调用内置的max() 函数来查找具有最高比率的SequenceMatcher 结果。首先,我们构建一个“生成器表达式”来尝试headwords 中的所有单词,并在每个单词上调用SequenceMatcher()。但是当我们完成后,我们也想知道这个词是什么。所以生成器表达式生成元组,其中元组中的第一个值是SequenceMatcher 结果,第二个值是单词。 max() 函数无法知道我们关心的是比率,所以我们必须告诉它;我们通过创建一个函数来测试我们关心的内容,然后将该函数作为key= 参数传递。现在max() 为我们找到了比率最高的值。 max() 使用生成器表达式生成的所有值并返回单个值,然后我们将其解压缩到变量 m 和 word 中。

    在 Python 中,最好使用像 sentence_list 这样的变量名而不是 sentenceList。请参阅以下指南:http://www.python.org/dev/peps/pep-0008/

    使用递增索引变量并分配到列表中的索引位置不是一个好习惯。而是从一个空列表开始,然后使用 .append() 方法函数来附加值。

    另外,你最好建立一个单词字典和它们的比率。

    请注意,您的原始代码似乎有一个错误:只要任何单词的百分比超过 0.86,所有单词都会保存在 sentenceList 中,无论它们的比例是多少。上面我写的代码,只保存单词自身比例足够高的单词。

    编辑:这是为了回答关于生成器表达式需要加括号的问题。

    每当我收到该错误消息时,我通常会自行拆分生成器表达式并将其分配给一个变量。像这样:

    def check_ratio(m):
        return m.ratio()
    
    y = y.lower()  # do the .lower() call one time
    genexp = ((SequenceMatcher(None, y, word), word) for word in headwords)
    m, word =  max(genexp, key=check_ratio)
    percentage = max(percentage, m.ratio())  # remember best ratio
    if m.ratio() > 0.86:
        setence_list.append(word)
    

    这就是我的建议。但是,如果您不介意复杂的行看起来更忙,您可以简单地添加一对额外的括号,如错误消息所示,因此生成器表达式完全被括号括起来。像这样:

    m, word =  max(((SequenceMatcher(None, y, word), word) for word in headwords), key=check_ratio)
    

    当您将表达式传递给函数时,Python 允许您省略围绕生成器表达式的显式括号,但前提是它是该函数的唯一参数。由于我们还传递了一个 key= 参数,因此我们需要一个完全带括号的生成器表达式。

    但我认为,如果您将 genexp 拆分为单独的行,则更易于阅读。

    编辑:@Peter Wood 指出文档建议重用 SequenceMatcher 以提高速度。我没有时间对此进行测试,但我认为这是正确的方法。

    很高兴,代码变得更简单了!总是一个好兆头。

    编辑:我刚刚测试了代码。这段代码对我有用;看看它是否适合你。

    from difflib import SequenceMatcher
    
    headwords = [
    # This is a list of 650,000 words
    # Dummy list:
        "happy",
        "new",
        "year",
    ]
    
    
    def words_from_file(filename):
        with open(filename, "rt") as f:
            for line in f:
                for word in line.split():
                    yield word
    
    def _match(matcher, s):
        matcher.set_seq2(s)
        return (matcher.ratio(), s)
    
    ratios = {}
    best_ratio = 0
    
    matcher = SequenceMatcher()
    
    for word in words_from_file("sentences.txt"):
        matcher.set_seq1(word.lower())
        if word not in headwords:
            ratio, word =  max(_match(matcher, word.lower()) for word in headwords)
            best_ratio = max(best_ratio, ratio)  # remember best ratio
            if ratio > 0.86:
                ratios[word] = ratio
    
    print(best_ratio)
    print(ratios)
    

    【讨论】:

    • Steveha,我觉得这种方法很有趣,我正在尝试使用它,但我收到一条错误消息,上面写着:“如果不是唯一参数,则生成器表达式必须加上括号”......有什么想法吗?
    • 另外,the docs suggest reusing a SequenceMatcher: 'SequenceMatcher 计算并缓存第二个序列的详细信息,所以如果你想将一个序列与多个序列进行比较,使用 set_seq2() 将常用序列设置一次,然后重复调用 set_seq1(),对每个其他序列调用一次。'
    【解决方案3】:

    1) 我会将headwordList 存储为一个集合,而不是一个列表,因为它是一个散列数据结构,因此可以更快地访问。

    2) 您已将sentenceList 定义为列表,然后尝试将其用作带有sentenceList[x] = y 的字典。我会专门为计数定义一个不同的结构。

    3) 你构造了不需要做的sentenceList。

    for line in file:
       if line not in headwordList...
    

    4) 你永远不会对line 进行标记,这意味着你将整行存储在 sentenceList 中换行符之前,并查看它是否在 wordlist 中

    【讨论】:

      【解决方案4】:

      这是一道数据结构题。您想要做的是将您的列表变成具有更快元素查找速度的东西,例如二叉搜索树在这里工作得很好:时间复杂度只有O (log n),而不是列表中的O (n)(在比较快得令人难以置信)。

      这里有一个相当简单的解释:

      http://interactivepython.org/runestone/static/pythonds/Trees/balanced.html

      但如果您不熟悉树的概念,您可能需要提前几章开始:

      http://interactivepython.org/runestone/static/pythonds/Trees/trees.html

      【讨论】:

      • 这不是 Python 问题的有用答案。 Python 包含几个可有效应用于此问题的内置函数(set 和 dict)。 @English Grad 说他/她不是专家软件工程师,因此显示如何实现数据结构的页面不会有帮助,即使是专家软件开发人员也最好使用 Python 内置而不是实现树形数据结构。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 2014-01-19
      • 2019-07-31
      • 2018-04-13
      • 2013-02-23
      相关资源
      最近更新 更多