需要帮助更快地进行排列

时间:2011-11-30 19:29:35

标签: python permutation

这是我的工作代码,我正在设法让它更快地找到有效的单词,我在考虑为每个单词制作单独的字典列表,你会怎么想?

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()

7 个答案:

答案 0 :(得分:5)

list1更改为套装:

set1 = set(list1)

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

答案 1 :(得分:5)

首先,对代码进行概要分析。这将告诉你缓慢的部分在哪里。

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

第三,考虑简化代码以删除不必要的陈述,例如

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

答案 2 :(得分:1)

你到底想要完成什么?看起来你已经有了一些你正在读的有效单词的字典。为什么要排列可以根据用户提供的输入构建的所有单词组合?

您需要考虑的算法。您创建的每个排列都会迭代字典中的每个已知单词(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 )

抱歉,我的python有点生疏,但希望你能理解。

答案 3 :(得分:1)

尝试用:

替换内部循环
for x in itertools.permutations(letters,length):
    word = ''.join(x)
    if word.upper() in words:
        answers.append(word)
        print(word)

答案 4 :(得分:1)

如果您过于热衷于提高速度而不考虑其可读性,则可以尝试以下

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]

答案 5 :(得分:1)

问题在于你的算法基本上是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 :(得分:0)

如果您打算经常查找单词,则应从数据中构建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%,而不是从头开始构建dict。

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