如何优化此Python代码以生成word-distance 1的所有单词?

时间:2009-04-25 02:20:27

标签: python optimization python-2.x levenshtein-distance word-diff

12 个答案:

答案 0 :(得分:24)

如果您的单词列表很长,从'word'生成所有可能的1个字母差异可能更有效,那么检查列表中的哪些?我不知道任何Python,但应该有一个合适的wordlist数据结构,允许进行日志时间查找。

我建议这样做,因为如果你的单词是合理的长度(~10个字母),那么你只会寻找250个潜在单词,如果你的单词列表大于几百个单词,这可能会更快。

答案 1 :(得分:10)

当你真正关心距离= 1时,你的函数distance正在计算总距离。大多数情况下,你会知道它在几个字符内是> 1,所以你可以提前返回并节省大量时间。

除此之外,可能有更好的算法,但我无法想到它。

编辑:另一个想法。

根据第一个字符是否匹配,您可以制作2个案例。如果不匹配,则单词的其余部分必须完全匹配,您可以一次性测试。否则,与你正在做的事情类似。你甚至可以递归地做,但我认为这不会更快。

def DifferentByOne(word1, word2):
    if word1[0] != word2[0]:
        return word1[1:] == word2[1:]
    same = True
    for i in range(1, len(word1)):
        if word1[i] != word2[i]:
            if same:
                same = False
            else:
                return False
    return not same

编辑2:我已经删除了检查以查看字符串是否长度相同,因为您说它是多余的。在我自己的代码和is_neighbors函数Ryan's tests上运行provided by MizardX,我得到以下结果:

  • 原始距离():3.7秒
  • My DifferentByOne():1.1秒
  • MizardX的is_neighbors():3.7秒

编辑3:(可能会进入社区维基地区,但是......)

使用izip而不是zip来尝试最终定义is_neighbors():2.9秒。

这是我的最新版本,仍然是1.1秒:

def DifferentByOne(word1, word2):
    if word1[0] != word2[0]:
        return word1[1:] == word2[1:]
    different = False
    for i in range(1, len(word1)):
        if word1[i] != word2[i]:
            if different:
                return False
            different = True
    return different

答案 2 :(得分:6)

from itertools import izip

def is_neighbors(word1,word2):
    different = False
    for c1,c2 in izip(word1,word2):
        if c1 != c2:
            if different:
                return False
            different = True
    return different

或者可能是izip代码的内容:

def is_neighbors(word1,word2):
    different = False
    next1 = iter(word1).next
    next2 = iter(word2).next
    try:
        while 1:
            if next1() != next2():
                if different:
                    return False
                different = True
    except StopIteration:
        pass
    return different

重写了getchildren

def iterchildren(word, wordlist):
    return ( w for w in wordlist if is_neighbors(word, w) )
  • izip(a,b)返回来自ab的值对的迭代器。
  • zip(a,b)会返回ab对的列表。

答案 3 :(得分:4)

人们主要通过尝试编写更快的功能来解决这个问题,但可能还有另一种方式......

  

“距离”被称为超过500万次

这是为什么?也许更好的优化方法是尝试减少调用distance的次数,而不是缩短distance's执行时间的毫秒数。没有看到完整的脚本就无法分辨,但通常不需要优化特定的功能。

如果这是不可能的,也许你可以把它写成C模块?

答案 4 :(得分:3)

使用相同的参数调用distance函数的频率是多少?一个简单的实现优化就是使用memoization

您可能还可以创建某种字典,其中包含字母和单词列表,它们之间存在差异,并在其中查找值。该数据结构既可以通过pickle存储和加载,也可以在启动时从头开始生成。

如果你使用的词很长,那么短路评估只会给你增益,因为你使用的汉明距离算法基本上是O(n),其中n是字长。

我做了一些关于时间的实验,以寻找可能具有说明性的替代方法。

时间结果

您的解决方案

d = """\
def distance(word1, word2):
    difference = 0
    for i in range(len(word1)):
        if word1[i] != word2[i]:
            difference += 1
    return difference
"""
t1 = timeit.Timer('distance("hello", "belko")', d)
print t1.timeit() # prints 6.502113536776391

一个班轮

d = """\
from itertools import izip
def hamdist(s1, s2):
    return sum(ch1 != ch2 for ch1, ch2 in izip(s1,s2))
"""
t2 = timeit.Timer('hamdist("hello", "belko")', d)
print t2.timeit() # prints 10.985101179

快捷方式评估

d = """\
def distance_is_one(word1, word2):
    diff = 0
    for i in xrange(len(word1)):
        if word1[i] != word2[i]:
            diff += 1
        if diff > 1:
            return False
    return diff == 1
"""
t3 = timeit.Timer('hamdist("hello", "belko")', d)
print t2.timeit() # prints 6.63337

答案 5 :(得分:2)

答案 6 :(得分:1)

对于这样一个具有如此大的性能影响的简单函数,我可能会创建一个C库并使用ctypes来调用它。 reddit的创始人之一声称他们使用这种技术使网站的速度提高了2倍。

你也可以在这个功能上使用psyco,但要注意它会占用大量内存。

答案 7 :(得分:0)

我不知道它是否会显着影响你的速度,但你可以先将列表理解转换为生成器表达式。它仍然是可迭代的,因此在使用方面不应该有太大的不同:

def getchildren(word, wordlist):
    return [ w for w in wordlist if distance(word, w) == 1 ]

def getchildren(word, wordlist):
    return ( w for w in wordlist if distance(word, w) == 1 )

主要的问题是列表理解会在内存中构建自己并占用相当多的空间,而生成器会动态创建列表,因此不需要存储整个事物。

另外,根据未知的答案,这可能是一种更加“pythonic”的写距离方式():

def distance(word1, word2):
    difference = 0
    for x,y in zip (word1, word2):
        if x == y:
            difference += 1
    return difference

但是当len(word1)!= len(word2)时它的意图令人困惑,在zip的情况下,它只返回与最短单词一样多的字符。 (这可能会成为一种优化......)

答案 8 :(得分:0)

试试这个:

def distance(word1, word2):
  return sum([not c1 == c2 for c1, c2 in zip(word1,word2)])

另外,你有游戏链接吗?我喜欢被文字游戏摧毁

答案 9 :(得分:0)

首先发生在我身上:

from operator import ne

def distance(word1, word2):
    return sum(map(ne, word1, word2))

它有比人们发布的其他函数更快的机会,因为它没有解释循环,只是调用Python原语。而且它足够短,你可以合理地将它内联到调用者。

对于更高级别的问题,我将研究在度量空间中为相似性搜索开发的数据结构,例如: this paperthis book,我从未读过这些文章(他们在寻找我读过但不记得的论文时出现过)。

答案 10 :(得分:0)

这个代码段:

for x,y in zip (word1, word2):
    if x != y:
        difference += 1
return difference

我会用这个:

return sum(1 for i in xrange(len(word1)) if word1[i] == word2[i])

相同的模式将遵循所提供的代码......

答案 11 :(得分:0)

其他人只关注明确的距离计算,而没有做任何关于构建距离-1候选者的事情。 您可以使用名为 Trie 的众所​​周知的数据结构来改进,以将隐式距离计算生成所有距离的任务合并-1邻居单词。 Trie是一个链表,其中每个节点代表一个字母,而“下一个”节点代表一个字母。 field是一个包含多达26个条目的dict,指向下一个节点。

这里是伪代码:迭代地按照你给出的单词走Trie;在每个节点处将所有distance-0和distance-1邻居添加到结果中;保持距离的计数器并减少距离。你不需要递归,只需要一个需要额外distance_so_far整数参数的查找函数。

通过为长度为3,长度为4,长度为5等单词构建单独的Tries,可以获得O(N)空间增加的额外速度的微小权衡。