我做了很多谷歌搜索,但没有找到任何东西,所以如果我只是在寻找错误的东西,我真的很抱歉。
我正在为Ghost编写MIT Introduction to Programming, assignment 5的实现。
作为其中的一部分,我需要确定一串字符是否是任何有效单词的开头。我有一个有效单词列表(“wordlist”)。
更新:我每次都可以使用在列表中迭代的内容,例如Peter的简单建议:
def word_exists(wordlist, word_fragment):
return any(w.startswith(word_fragment) for w in wordlist)
我之前有过:
wordlist = [w for w in wordlist if w.startswith(word_fragment)]
(从here)将列表缩小到以该片段开头的有效单词列表,如果wordlist为空则认为它是一个丢失。我采用这种方法的原因是我(错误地,见下文)认为这将节省时间,因为后续查找只需搜索较小的列表。
在我看来,这是通过原始词汇表中的每个项目(38,000个单词)检查每个项目的开始。当wordlist被排序时,这似乎很愚蠢,一旦它击中单词片段之后的某些内容,理解就会停止。我试过这个:
newlist = []
for w in wordlist:
if w[:len(word_fragment)] > word_fragment:
# Take advantage of the fact that the list is sorted
break
if w.startswith(word_fragment):
newlist.append(w)
return newlist
但是这个速度大致相同,我认为可能是因为列表推导作为编译代码运行了?
然后我认为再次更高效的是列表中的某种形式的二进制搜索,以找到匹配单词的块。这是要走的路,还是我错过了一些非常明显的东西?
显然,在这种情况下这并不是什么大不了的事,但我刚开始编程并且想要正确地做事。
我已经用simple test script测试了以下建议。虽然Peter的二分搜索/ bisect对于单次运行显然会更好,但我对缩小列表是否会胜过一系列片段感兴趣。事实上,它没有:
The totals for all strings "p", "py", "pyt", "pyth", "pytho" are as follows:
In total, Peter's simple test took 0.175472736359
In total, Peter's bisect left test took 9.36985015869e-05
In total, the list comprehension took 0.0499348640442
In total, Neil G's bisect took 0.000373601913452
创建第二个列表等的开销明显比搜索更长的列表花费更多时间。事后看来,这可能是最好的方法,因为“减少列表”方法增加第一次运行的时间,这是最糟糕的情况。
感谢所有人提出了一些很好的建议,并为彼得做了最好的答案!
答案 0 :(得分:14)
生成器表达式被懒惰地评估,所以如果你只需要确定你的单词是否有效,我希望以下内容更有效率,因为它不一定会强制它在找到一个完整列表后构建它匹配:
def word_exists(wordlist, word_fragment):
return any(w.startswith(word_fragment) for w in wordlist)
请注意,缺少方括号对于此工作非常重要。
然而,在最坏的情况下,这显然仍是线性的。你是正确的,二元搜索会更有效率;你可以使用内置的bisect
模块。它可能看起来像这样:
from bisect import bisect_left
def word_exists(wordlist, word_fragment):
try:
return wordlist[bisect_left(wordlist, word_fragment)].startswith(word_fragment)
except IndexError:
return False # word_fragment is greater than all entries in wordlist
bisect_left
在O(log(n))中运行,因此对于大型词表来说会快得多。
编辑:如果你的word_fragment真的很普通(比如't'),我猜想你给出的例子会失败,在这种情况下,它可能花费大部分时间来组合一大堆有效单词,并获得收益从只需要对列表进行部分扫描就可以忽略不计了。很难说肯定,但它有点学术,因为无论如何二进制搜索更好。
答案 1 :(得分:4)
你是对的,因为列表已经排序,你可以更有效地做到这一点。
我正在建立@Peter的答案,它会返回一个元素。我看到你想要所有以给定前缀开头的单词。这是你如何做到的:
from bisect import bisect_left
wordlist[bisect_left(wordlist, word_fragment):
bisect_left(wordlist, word_fragment[:-1] + chr(ord(word_fragment[-1])+1))]
这将返回原始排序列表中的切片。
答案 2 :(得分:1)
彼得建议我使用Bisect模块。特别是如果你正在阅读一大堆文字。
如果你真的需要速度,你可以制作一个具有适合任务的预处理数据结构的守护进程(How do you create a daemon in Python?)
我建议你可以使用“尝试”
http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=usingTries
索引和搜索有许多算法和数据结构 文本中的字符串,其中一些包含在标准中 图书馆,但不是全部; trie数据结构很好 一个不是的例子。
让word成为单个字符串,让字典成为一大组 话。如果我们有一本字典,我们需要知道一个单词 在字典里面,try是一个可以的数据结构 帮我们。但你可能会问自己,“为什么要设置尝试 和散列表可以做同样的事情吗?“有两个主要原因:
- 尝试可以在O(L)时间内插入和查找字符串(其中L代表 一个单词的长度)。这比设置快得多,但是它 比哈希表快一点。
- 集合和哈希表 只能在字典中找到与单身完全匹配的单词 我们找到的词;特里允许我们找到有文字的单词 单个字符不同,前缀共同,字符缺失, 等。
尝试在TopCoder问题中很有用,但也有一个 软件工程中的大量应用。例如, 考虑一个Web浏览器。你知道网络浏览器是如何自动的 完成您的文本或向您展示您的文本的许多可能性 可写吗?是的,有了特里,你可以非常快地完成。你呢 知道正交校正器如何检查你的每一个字 类型是在字典中?再一次特里。你也可以使用trie 建议对文本中出现的单词进行更正,但是 不在字典里。
一个例子是:
start={'a':nodea,'b':nodeb,'c':nodec...}
nodea={'a':nodeaa,'b':nodeab,'c':nodeac...}
nodeb={'a':nodeba,'b':nodebb,'c':nodebc...}
etc..
然后,如果你想要所有以ab开头的单词,你就会遍历 开始['a'] ['b']这就是你想要的所有单词。
要构建它,你可以遍历你的单词列表,并为每个单词,遍历字符,在必要时添加一个新的默认字典。
答案 3 :(得分:0)
在二进制搜索的情况下(假设wordlist已排序),我正在考虑这样的事情:
wordlist = "ab", "abc", "bc", "bcf", "bct", "cft", "k", "l", "m"
fragment = "bc"
a, m, b = 0, 0, len(wordlist)-1
iterations = 0
while True:
if (a + b) / 2 == m: break # endless loop = nothing found
m = (a + b) / 2
iterations += 1
if wordlist[m].startswith(fragment): break # found word
if wordlist[m] > fragment >= wordlist[a]: a, b = a, m
elif wordlist[b] >= fragment >= wordlist[m]: a, b = m, b
if wordlist[m].startswith(fragment):
print wordlist[m], iterations
else:
print "Not found", iterations
它会找到一个匹配的单词,或者没有。然后,您必须查看其左侧和右侧以查找其他匹配的单词。我的算法可能不正确,它只是我思想的粗略版本。
答案 4 :(得分:0)
这是我将列表 wordlist 缩小到以给定片段开头的有效单词列表的最快方法:
sect()是一个生成器函数,它使用优秀的Peter的想法来使用 bisect 和 islice()函数:
from bisect import bisect_left
from itertools import islice
from time import clock
A,B = [],[]
iterations = 5
repetition = 10
with open('words.txt') as f:
wordlist = f.read().split()
wordlist.sort()
print 'wordlist[0:10]==',wordlist[0:10]
def sect(wordlist,word_fragment):
lgth = len(word_fragment)
for w in islice(wordlist,bisect_left(wordlist, word_fragment),None):
if w[0:lgth]==word_fragment:
yield w
else:
break
def hooloo(wordlist,word_fragment):
usque = len(word_fragment)
for w in wordlist:
if w[:usque] > word_fragment:
break
if w.startswith(word_fragment):
yield w
for rep in xrange(repetition):
te = clock()
for i in xrange(iterations):
newlistA = list(sect(wordlist,'VEST'))
A.append(clock()-te)
te = clock()
for i in xrange(iterations):
newlistB = list(hooloo(wordlist,'VEST'))
B.append(clock() - te)
print '\niterations =',iterations,' number of tries:',repetition,'\n'
print newlistA,'\n',min(A),'\n'
print newlistB,'\n',min(B),'\n'
结果
wordlist[0:10]== ['AA', 'AAH', 'AAHED', 'AAHING', 'AAHS', 'AAL', 'AALII', 'AALIIS', 'AALS', 'AARDVARK']
iterations = 5 number of tries: 30
['VEST', 'VESTA', 'VESTAL', 'VESTALLY', 'VESTALS', 'VESTAS', 'VESTED', 'VESTEE', 'VESTEES', 'VESTIARY', 'VESTIGE', 'VESTIGES', 'VESTIGIA', 'VESTING', 'VESTINGS', 'VESTLESS', 'VESTLIKE', 'VESTMENT', 'VESTRAL', 'VESTRIES', 'VESTRY', 'VESTS', 'VESTURAL', 'VESTURE', 'VESTURED', 'VESTURES']
0.0286089433154
['VEST', 'VESTA', 'VESTAL', 'VESTALLY', 'VESTALS', 'VESTAS', 'VESTED', 'VESTEE', 'VESTEES', 'VESTIARY', 'VESTIGE', 'VESTIGES', 'VESTIGIA', 'VESTING', 'VESTINGS', 'VESTLESS', 'VESTLIKE', 'VESTMENT', 'VESTRAL', 'VESTRIES', 'VESTRY', 'VESTS', 'VESTURAL', 'VESTURE', 'VESTURED', 'VESTURES']
0.415578236899
sect()比 holloo()
快14.5倍PS:
我知道 timeit 的存在,但是在这里,对于这样的结果, clock()就足够了
答案 5 :(得分:-3)
在列表中进行二进制搜索并不能保证任何事情。我不确定这是怎么回事。
你有一个订购的清单,这是一个好消息。你的两种情况的算法性能复杂度都是O(n),这也不错,你只需要迭代整个wordlist一次。
但在第二种情况下,性能(工程性能)应该更好,因为一旦发现其他情况不适用,您就会破裂。尝试列出第一个元素匹配的列表,其余的38000个元素不匹配,第二个元素将匹配第一个元素。