Python-找到可以在单词中找到的所有子词

时间:2011-08-03 21:50:16

标签: python algorithm

最后,我想找出英文词典中哪个词包含至少三个字母的最多子词。我写了这个算法,但它太慢而无法使用。想知道我可以优化它的方法

def subWords(word):
    return set((word[0:i] for i in range(2, len(word)+1))) #returns all subWords of length 2 or greater

def checkDict(wordList, dictList):
    return set((word for word in wordList if word in dictList))

def main():
    dictList = [i.strip() for i in open('wordlist.txt').readlines()]
    allwords = list()
    maximum = (0, list())

    for dictWords in dictList:
        for i in range (len(dictWords)):
            for a in checkDict(subWords(dictWords[i: len(dictWords) + 1]), dictList):
                allwords.append(a)

        if len(allwords) > maximum[0]:
            maximum = (len(allwords), allwords)

        print maximum
        allwords = list()

    print maximum 
main()

5 个答案:

答案 0 :(得分:7)

算法的主要缺点是,对于每个子字,您需要将其与字典中的每个其他字进行比较。你不需要这样做,真的 - 如果你的单词以'a'开头,你真的不需要看它是否匹配以'b'开头的单词。如果下一个字母是'c',那么你真的不想将它与以'd'开头的单词进行比较。那么问题就变成:“我如何有效地实现这个想法?”

为此,我们可以创建一个树来表示字典中的所有单词。我们通过获取字典中的每个单词并使用它扩展树,并在最后一个节点中进行着色来构造此树。

Example Tree

当我们想要测试子树是否在这个树中时,我们只是逐字逐句地查看该单词并使用这些字母来确定树中的下一个位置(从顶部开始)。如果我们发现我们无处可去,或者在经过整个子词后我们落在非阴影树节点上,那么它就不是一个字。否则,如果我们落在阴影节点上,它就是一个单词。这样做的结果是我们可以一次搜索整个字典 ,而不是一次搜索一个字。当然,这样做的成本在一开始就是一些设置,但如果你在字典里有很多单词,这不是一个很好的代价。

嗯,这太棒了!让我们尝试实现它:

class Node:
    def __init__( self, parent, valid_subword ):
        self.parent = parent
        self.valid_subword = valid_subword
        self.children = {}

    #Extend the tree with a new node
    def extend( self, transition, makes_valid_word ):
        next_node = None
        if transition in self.children:
            if makes_valid_word:
                self.children[transition].makes_valid_word = True
        else:
            self.children[transition] = Node( self, makes_valid_word )
        return self.children[transition]

def generateTree( allwords ):
  tree = Node( None, False )
    for word in allwords:
      makes_valid_word = False
      current_node = tree
      for i in range(len(word)):
        current_node = current_node.extend( word[i], True if i == len(word) - 1 else False )
  return tree

def checkDict( word, tree ):
    current_node = tree
    for letter in word:
        try:
            current_node = current_node.children[letter]
        except KeyError:
            return False

    return current_node.valid_subword

然后,后来:

for word in allWords:
  for subword in subWords(word):
    checkDict(subword)
    #Code to keep track of the number of words found, like you already have

此算法允许您在 O(m)时间内检查词典中是否有单词,其中m是词典中最长单词的长度。请注意,对于包含任意数量单词的字典,这仍然大致保持不变。您的原始算法每次检查 O(n),其中n是字典中的字数。

答案 1 :(得分:6)

1)风格和组织:使用单个函数生成单词的所有子词更有意义。

2)样式:使用set不需要双括号。

3)表现(我希望):从你正在查找的单词中做出set;那么你可以使用内置的交集检查。

4)性能(几乎可以肯定):不要手动循环来找到最大元素;使用内置的max。你可以直接比较(长度,元素)元组; Python从头到尾比较每对元素的元组,就像每个元素都是字符串中的字母一样。

5)表演(可能):确保词典中没有单词或双字母单词,因为它们只是妨碍了。

6)表现(遗憾的是):不要将一切分解为一个功能。

7)样式:文件I / O应使用with块来确保正确清理资源,并且文件迭代器默认迭代行,因此我们可以隐式地获取行列表而不必调用.readlines()

我最终(没有经过适当测试,除了'片段'表达式):

def countedSubWords(word, dictionary): 
  fragments = set(
    word[i:j]
    for i in range(len(word)) for j in range(i+3, len(word)+1)
  )
  subWords = fragments.intersection(dictionary)
  return (len(subWords), subWords)


def main():
  with open('wordlist.txt') as words:
    dictionary = set(word.strip() for word in words if len(word.strip()) > 2)
    print max(countedSubWords(word, dictionary) for word in dictionary)

答案 2 :(得分:3)

要探索基本的Python,请看一下这个函数(基本上是一个更快,更完美,PEP8 - JBernardo和Karl Knechtel建议的快乐版本:

def check_dict(word, dictionary): 
  """Return all subwords of `word` that are in `dictionary`."""
  fragments = set(word[i:j] 
                  for i in xrange(len(word) - 2) 
                  for j in xrange(i + 3, len(word) + 1))
  return fragments & dictionary

dictionary = frozenset(word for word in word_list if len(word) >= 3)
print max(((word, check_dict(word, dictionary)) for word in dictionary), 
          key=lambda (word, subwords): len(subwords)) # max = the most subwords

输出类似:

('greatgrandmothers',
set(['and', 'rand', 'great', 'her', 'mothers', 'moth', 'mother', 'others', 'grandmothers', 'grandmother', 'ran', 'other', 'greatgrandmothers', 'greatgrandmother', 'grand', 'hers', 'the', 'eat']))

来自http://www.mieliestronk.com/wordlist.html的单词列表。


现在我知道你不是为了表演(上面的代码已经<1s,标准英语词汇为58k字)。

但是如果您需要以超快的速度运行,请在某些内循环中说:)

  • 您希望避免在堆上check_dict内创建所有子串的副本,这是主要的性能杀手。
  • 你可以通过指针算术来做到这一点,只用指针分隔符表示子串(而不是完整的对象)。
  • 使用该子字符串快速确定它是否是有效词汇表的一部分:
    • 使用trie数据结构或其内存友好版本PATRICIA tree
    • 从字典中构建一次静态trie,然后执行快速子字符串查找
    • 逐步改变指针以探索所有可能的子串,增加有效单词的命中计数器
    • 这样你可以避免任何动态分配(没有字符串,没有集合),快速闪耀!!
  • 所有这些在Python中都不是很相关,因为这样的内存管理太低级了,无论如何,使用Python执行性能关键代码 会更好。

答案 3 :(得分:1)

这会在几秒钟内完成。 “sowpods.txt”有267627个3个或更多字母的单词 如果您使用的是Python2.5或2.6,则需要使用at_least_3 = set(w for w in words if len(w)>=3)

words = open("sowpods.txt").read().split()

at_least_3 = {w for w in words if len(w)>=3}

def count_subwords(word):
    counter = 0
    for i in range(len(word)-2):
        for j in range(i+3,len(word)+1):
            candidate = word[i:j]
            if candidate in at_least_3:
                counter += 1
    return counter

for row in sorted((count_subwords(w),w) for w in at_least_3):
    print row

子字数最多为26

(26, 'CORESEARCHERS')
(26, 'FOREGONENESSES')
(26, 'METAGENETICALLY')
(26, 'PREPOSSESSIONS')
(26, 'SACRAMENTALISTS')
(26, 'WHOLESOMENESSES')

答案 4 :(得分:0)

这就是你要问的或者我错过了什么?

>>> words = ['a', 'asd', 'asdf', 'bla']
>>> [sum(1 for i in (a for a in words if a in b)) for b in words]
[1, 2, 3, 2]

这是每个单词中的单词数量。如果你不想计算少于3个字符的单词,只需删除它们......

当然,它是O(n²)

编辑:

问题要求所有子词,但代码只询问具有更多子词的子句...如果你真的想要第一个行为,只需删除sum(...)部分并使genexp成为列表理解...