Python搜索大型列表的速度

12

我在搜索一个非常大的列表时遇到了速度问题。我的文件里有很多错误和奇怪的单词。我正在尝试使用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

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


2
我不同意。他更多或少在寻找替代方法。 - keyser
2
这是一个数据结构问题。 - wim
2
我能立即看到的一件事是将headwordList改为集合而不是列表,以获得更好的查找性能,用于那些in检查。 - wim
1
这是基于现有算法还是你在试图将一些东西拼凑在一起?特定的“0.86”让我想到,如果我们知道原始问题,也许我们可以建议一个更好的整体解决方案。 - Izkata
1
@EnglishGrad,这将极大地帮助到 if y not in headwordList: 部分。 - alko
显示剩余6条评论
4个回答

7

两个小提示可能会有所帮助:

1)使用此 SO 回答中的方法来最有效地读取大文件。

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次。


4
你应该把 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()函数,在headwords中尝试所有单词并找到具有最高比率的SequenceMatcher结果。首先,我们构建一个“生成器表达式”,在其中对每个单词调用SequenceMatcher()。但当我们完成时,我们还想知道单词是什么。因此,生成器表达式会产生元组,其中元组中的第一个值是SequenceMatcher结果,第二个值是单词。由于max()函数无法知道我们关心的内容是比率,因此我们必须告诉它。我们通过创建测试我们关心的内容的函数来实现这一点,然后将该函数作为key=参数传递。现在max()可以为我们找到具有最高比率的值。 max()消耗生成器表达式产生的所有值并返回单个值,然后我们将其解包到变量mword中。
在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,我觉得这种方法很有趣,正在尝试使用它,但是我遇到了一个错误消息,上面写着:“如果不是唯一的参数,则必须将生成器表达式括在括号中”,你有什么想法吗? - English Grad
此外,文档建议重复使用SequenceMatcher:'SequenceMatcher计算并缓存有关第二个序列的详细信息,因此如果您想将一个序列与多个序列进行比较,请使用set_seq2()一次设置常用序列,并为每个其他序列重复调用set_seq1()。' - Peter Wood

3

1)我建议将headwordList存储为集合而不是列表,因为它是一种哈希数据结构,可以更快地访问。

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

3)您构造了sentenceList,这是不必要的。

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

4)您从未将line分词,这意味着您在句子列表中存储了整个换行符之前的行,并查看它是否在单词列表中。


0
这是一个数据结构问题。你想要做的是将列表转换为具有更快元素查找速度的东西,例如二叉搜索树在这里非常适用:时间复杂度仅为O(log n),而列表的时间复杂度为O(n)(与之相比,列表的速度非常快)。
这里有一个相当简单的解释:

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

但是如果你不熟悉树的概念,你可能想从前面几章开始:

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


2
这不是一个有用的Python问题答案。Python包含几个内置函数可以应用于此问题(setdict)。@English Grad说他/她不是专业软件工程师,因此展示如何实现数据结构的页面并不能帮助他/她,即使是专业软件开发人员也最好使用Python内置函数而不是实现树形数据结构。 - steveha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接