Python搜索大列表速度

时间:2013-12-23 21:12:32

标签: python list memory optimization text

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

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

4 个答案:

答案 0 :(得分: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()函数来查找具有最高比率的SequenceMatcher结果。首先,我们构建一个“生成器表达式”,尝试headwords中的所有单词,在每个单词上调用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)

答案 1 :(得分:4)

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

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 :(得分:3)

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

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

3)构建sentenceList,不需要这样做。

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

4)你永远不会对line进行标记,这意味着你将整行保存在sentenceList中的换行符之前,看看它是否在单词列表中

答案 3 :(得分:0)

这是一个数据结构问题。您想要做的是将列表转换为具有更快元素查找速度的内容,例如二叉搜索树在这里工作得很好:时间复杂度仅为O (log n)而不是列表中的O (n) (相比之下非常快)。

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

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

但是如果您不熟悉树概念,您可能希望在几个章节之前开始:

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