【问题标题】:Need help making a permutation faster需要帮助更快地进行排列
【发布时间】:2012-01-10 00:45:33
【问题描述】:

这是我的工作代码,我正在尝试找到更快地找到有效单词的方法,我正在考虑可能为每个单词制作单独的字典列表,你们怎么看?

import random
import itertools

file_name='words.txt'

def load_words():
    try:
        f=open(file_name,'r')
        str1=f.read()
        f.close()
    except:
        print('Problem opening the file',file_name)
    list1=[]
    list1=str1.split()
    return(list1)

def is_valid(str1,list1):
    valid=False
    if str1 in list1:
        valid=True
    return valid

def generate(words,letters):
    answers=[]
    for length in range(2,len(letters)+1):
        for x in itertools.permutations(letters,length):
            word=''
            for let in x:
                word+=let
            if is_valid(word.upper(),words):
                answers.append(word)
                print(word)
    print(answers)

def main():
    words=load_words()
    letters = input('Enter your letters')
    answers = generate(words,letters)

main()

【问题讨论】:

    标签: python permutation


    【解决方案1】:

    首先,分析代码。这会告诉你慢的部分在哪里。

    其次,您可以考虑将单词列表转换为一个集合,它应该有一个更快的“in”运算符来检查单词是否存在。

    第三,考虑简化代码以删除不必要的语句,例如。

    def is_valid(str1,list1):
       return str1 in list1
    

    【讨论】:

    • 较慢的部分是有效单词为5个字符或更大时,我相信我根据eumiro的评论将其更改为一组,但我觉得速度没有变化,我也不明白你的第三个声明。
    • 第三个建议仅仅是清理你的代码。 list1 中的 str1 返回一个布尔值 True 或 False,因此您不需要 If ... return True 否则返回 False 因为 If 中的条件对其求值,所以只需返回它。消除它不会使您的算法更快,但它有助于减少代码大小并提高可读性。
    • 它实际上可能会缩短执行时间,因为冗余语句仍可能会被执行,这取决于 Python 在编译所有语句方面做得有多好。但这不太可能产生重大影响:我的主要建议仍然是分析代码。
    • Kylotan,你所说的剖析到底是什么意思,你能给我举个例子吗?谢谢
    • Profiling 是一种自动分析代码以查看调用的内容、调用频率和调用时间的行为。另见:docs.python.org/library/profile.html
    【解决方案2】:

    将您的list1 更改为一组:

    set1 = set(list1)
    

    如果您经常进行测试并且列表很长,那么测试str1 in set1 将比str1 in list1很多

    【讨论】:

    • 感谢您的评论,我觉得它并不快,如果您使用程序并输入 7 个以上的字母运行它,您会发现它非常慢。
    • 集合比列表快几个数量级。
    • @BrandonRutledge:不要依赖“感觉”来优化程序。使用类似“timeit”模块的东西来测试这些假设。 (docs.python.org/library/timeit.html)
    【解决方案3】:

    如果您太热衷于以降低可读性为代价来提高速度,您可以尝试以下方法

    def is_valid(str1,list1):
        return str1 in list1
    words=["BAD","CAB","BEC"]
    def generate2(words,letters):
        answers=[]
        [[answers.append(''.join(x).upper()) for x in itertools.permutations(letters,length) if ''.join(x).upper() in words] for length in range(2,len(letters)+1)]
        #print(answers)
        return answers
    

    List comprehension is faster than loops。所以将两个循环组合成一个理解。除此之外声明

           word=''
            for let in x:
                word+=let
            if is_valid(word.upper(),words):
    

    可以组合成is_valid(''.join(x).upper,words)甚至''.join(x).upper in words,记住函数调用代价高昂。

    我对速度进行了比较,看起来列表理解的运行速度提高了 50%。

    现在由你来决定


    >>> stmt1="""
    def is_valid(str1,list1):
        valid=False
        if str1 in list1:
            valid=True
        return valid
    words=["BAD","CAB","BEC"]
    def generate1(words,letters):
        answers=[]
        for length in range(2,len(letters)+1):
            for x in itertools.permutations(letters,length):
                word=''
                for let in x:
                    word+=let
                if is_valid(word.upper(),words):
                    answers.append(word)
                    #print(word)
        #print(answers)
        return answers
    generate1(words,['A','B','C','D','E'])
    """
    >>> 
    >>> stmt2="""
    def is_valid(str1,list1):
        return str1 in list1
    words=["BAD","CAB","BEC"]
    def generate2(words,letters):
        answers=[]
        [[answers.append(''.join(x).upper()) for x in itertools.permutations(letters,length) if ''.join(x).upper() in words] for length in range(2,len(letters)+1)]
        #print(answers)
        return answers
    generate2(words,['A','B','C','D','E'])
    """
    >>> 
    >>> t1=timeit.Timer(stmt=stmt1)
    >>> t2=timeit.Timer(stmt=stmt2)
    >>> t1.repeat(number=1000)
    >>> t2=timeit.Timer(stmt=stmt2)
    >>> t1.repeat(number=1000)
    [0.47923321640178074, 0.4353549521401874, 0.4362746333173959]
    >>> t2.repeat(number=1000)
    [0.2536238984591819, 0.2470974750062851, 0.24726312027155473]
    

    【讨论】:

      【解决方案4】:

      尝试将内部循环替换为:

      for x in itertools.permutations(letters,length):
          word = ''.join(x)
          if word.upper() in words:
              answers.append(word)
              print(word)
      

      【讨论】:

        【解决方案5】:

        问题在于您的算法基本上是 O(n * m!),其中 n 是单词列表大小,m 是字母数。将单词列表更改为集合应该会使搜索记录时间并将性能提高到 O( log(n) * m!),正如其他人在这里所推荐的那样。

        然而,真正的性能提升将来自完全消除搜索的字母排列。首先按字母顺序对单词列表中每个单词中的字母进行排序;它应该花费 O( n * p log(p) ) 时间,其中 p 是平均字长。然后在 O( n * log(n) ) 时间内按字母顺序对整个列表进行排序。还要跟踪原始单词,以便您可以从排序单词列表中的字符串转到 O(1) 中的原始单词。接下来按字母顺序对估算的字母进行排序,并在排序的单词列表中搜索它们。

        上述算法中最慢的操作是对按字母排序的字符串列表进行排序,即 O(n Log(n) )。搜索这样一个列表是 Log(n) 并导致 整个算法在 O(n Log(n) ) 时间内执行。它应该线性缩放到 m,即输入的字母数。

        实现留给读者。

        【讨论】:

          【解决方案6】:

          你到底想用这个来完成什么?看起来你已经有一些你正在阅读的有效单词字典。你为什么要排列可以从用户给出的输入构建的所有单词组合?

          您需要考虑的有关您的算法的一些问题。您创建的每个排列都在迭代字典中的每个已知单词(list1)。当您创建单词的所有排列时,您正在创建 m!用户给出的 m 个字母的单词。

          你基本上有 O(n * m!)。即使对于像 7 这样的少量事物来说,这也是非常大的。通过使用集合而不是列表,您可以将 n 项减少到一个常数,从而将您的算法更改为 O(m!),这仍然太大了。 如果我不得不猜测这个算法在做什么,我会说你想知道你可以从给定的字母中创建多少个已知单词。再说一次,你没有这么说,但我们假设这就是你的意思。

          更快的算法是遍历字典中的每个单词,看看是否可以通过从输入中挑选字母来生成该单词。所以你只遍历字典一次 O(n * m)。这消除了对您的输入进行排列的需要。算法如下:

           user_input = input("Give me some words")
           for word in list1:
               current = user_input
               found = True
               for letter in word:
                   if letter in current:
                      current.remove( letter )
                   else
                      found = False
                      break;
               if found:
                  answers.add( word )
           print( answers )
          

          对不起,我的蟒蛇有点生锈,但希望你能明白。

          【讨论】:

            【解决方案7】:

            如果您打算经常查找单词,您应该根据您的数据构建一个tree

            下面是一个简单的例子。代码应该是不言自明的,但如果有不清楚的地方请询问。

            import pickle
            
            
            class Tree:
                def __init__(self):
                    self.letters = dict()
            
                def add_words(self, words):
                    for word in words:
                        self.add_word(word)
            
                def add_word(self, word):
                    chars = list(word.lower())
                    l = chars.pop(0)
                    try:
                        self.letters[l].add_word(chars)
                    except KeyError:
                        self.letters[l] = Letter(l)
                        self.letters[l].add_word(chars)
            
                def is_word(self, word):
                    chars = list(word.lower())
                    l = chars.pop(0)
                    try:
                        return self.letters[l].is_word(chars)
                    except KeyError:
                        return False
            
            
            class Letter:
                def __init__(self, letter):
                    self.letter = letter
                    self.sub_letters = dict()
                    self.is_a_word = False
            
                def add_word(self, word):
                    if len(word) == 0:
                        self.is_a_word = True
                        return
                    l = word.pop(0)
                    try:
                        self.sub_letters[l].add_word(word)
                    except KeyError:
                        self.sub_letters[l] = Letter(l)
                        self.sub_letters[l].add_word(word)
            
                def is_word(self, word):
                    if len(word) == 0:
                        return self.is_a_word
                    l = word.pop(0)
                    try:
                        return self.sub_letters[l].is_word(word)
                    except KeyError:
                        return False
            
            
            def get_dict(obj_file, dict_file):
                try:
                    with open(obj_file, 'rb') as my_dict:
                        return pickle.load(my_dict)
                except IOError:
                    my_tree = Tree()
                    with open(dict_file, 'rb') as in_file:
                        for word in in_file:
                            my_tree.add_word(word.strip())
                    with open(obj_file, 'wb') as outfile:
                        pickle.dump(my_tree, outfile, pickle.HIGHEST_PROTOCOL)
                    return my_tree
            
            
            obj_file = 'mydict.pk'
            dict_file = 'wordlist.txt'
            my_tree = get_dict(obj_file, dict_file)
            

            (有很多不同类型的树,这只是一个非常简单的例子)

            构建树后,只需要调用len(word) 函数来确定输入的单词是否有效。这是与if word in wordlist 相比的巨大改进,后者采用了O(len(wordlist))

            这种方法的缺点是生成树可能需要一些时间。通过使用pickle 序列化Tree() 对象,您不必在每次启动脚本时都构建树。

            我尝试使用来自SIL International 的单词表(总共 109582 个单词)构建一棵树。

            使用timeit 计时,当解压目标文件而不是从头开始构建字典时,执行时间减少了约 50 %。

            如果您只想检查排列,您应该首先更改Tree()add_word() 方法以对字母进行排序。 Tree.is_word() 的输入参数当然也应该排序。

            【讨论】:

            • ...然后我意识到这是一个两年前的问题。好吧,好吧。
            猜你喜欢
            • 1970-01-01
            • 2014-04-23
            • 1970-01-01
            • 2012-11-14
            • 1970-01-01
            • 2014-06-29
            • 2015-06-28
            • 2010-11-07
            相关资源
            最近更新 更多