提高Python谜题的性能

时间:2014-07-04 21:16:21

标签: python performance

我得到了以下谜题来解决以获得面试,而不是我昨天收到通知我没有被选中(无法让这个谜题表现得那么好),我想知道是否有人可能知道如何帮我解决这个问题,让自己表现更好。它是用Python编写的,虽然我在一两年前有两个Python课程,但与我以前工作的世界相比,我还是新手(18年的嵌入式C!)任何帮助或建议这样我就可以从欣赏的经验中学习。提交难题提交,以考虑被选中参加面试。

益智挑战描述鉴于:

  

考虑一个"字"任何大写字母序列A-Z(不仅限于"词典单词")。对于具有至少两个不同字母的任何单词,还有其他单词由相同的字母组成,但顺序不同(例如,STATIONARILY / ANTIROYALIST,它们都是字典单词;为了我们的目的" AAIILNORSTTY&#34 ;也是一个"字"由与这两个字母相同的字母组成)。然后,我们可以根据每个单词的位置为每个单词分配一个数字,该单词位于由同一组字母组成的所有单词的按字母顺序排序的列表中。一种方法是生成整个单词列表并找到所需单词,但如果单词很长,这将会很慢。编写一个程序,它将一个单词作为命令行参数,并将其编号打印到标准输出。不要使用上面生成整个列表的方法。你的程序应该能够接受20个字母或更少的字母(可能重复一些字母),并且应该使用不超过1 GB的内存并运行不超过500毫秒。我们检查的任何答案都适合64位整数。

     

示例单词及其等级:

ABAB = 2
AAAB = 1
BAAA = 4
QUESTION = 24572
BOOKKEEPER = 10743
NONINTUITIVENESS = 8222334634
     

您的程序将根据其运行速度以及代码编写的清晰程度来判断。我们
  将运行您的程序以及阅读源代码,所以您可以采取任何措施来简化此过程。

运行此谜题:您可以在命令行输入中输入一个单词(这是它所处的当前状态),或者如果您想从文件中读取上面提供的单词,则可以注释掉{ {1}}只需一个字,然后通过取消注释该代码来阅读raw_input文件。

在计划的主要部分:

从命令行逐字输入 - 当前代码状态 - 将采用命令行字输入 words.txt - 以这种方式运行:命令行:getInputFromCommandLine()

如果您想从python athenaPuzzleIterDeep.py获取输入,请取消注释以下内容,这是一个要读取的文字文件 words.txt将随代码一起发送 - 以这种方式运行:命令行:words.txt - 但是您还必须将python athenaPuzzleIterDeep.py文件放在与python程序相同的目录中 words.txt

wordList = loadWords()

调查的性能增强不会最终变得足够好:迭代深化: 迭代加深试图通过BFS(广度优先搜索)时间和浅解决方案优势获得DFS(深度优先搜索)空间优势。因此可以尝试运行具有深度限制的DFS:尝试树的深度= 1,然后是2,3,......等。因此,不是在每个树级别构建整个图形,而是调用DFS以查看是否找到了解决方案。 DFS将首先搜索树的子节点的左侧,但最终将搜索每个节点,因此在不占用太多空间的情况下花费太多时间。但是,如果您使用BFS中的级别限制思想,只能逐级构建树级,然后使用DFS进行搜索,这就是迭代深化的想法。

Iterative Deepening没有提供所需的性能改进。我还尝试包含优先级队列python导入,但无法在我的linux版本上正确安装。

包含Words.txt文件:

wordNumberOrdering(wordList)

以下是代码:

ABAB
AAAB
BAAA
QUESTION
ABCDEFGHIJKLMNOPQRSTUVWXYZ
BOOKKEEPER
BOOKKEEPERS
STATIONARILY
NONINTUITIVENESS

3 个答案:

答案 0 :(得分:2)

我认为这里的关键在于对排名不同的排列进行思考。例如,鉴于BAAA,我们知道索引是> =所有A ***的索引,所以如果我们可以计算有多少,我们不需要明确地通过它们。那里有多少?嗯,这就是A ***有多少不同的排列。这很容易计算,但是我们必须弄清楚我们所有B ***中的位置 - 并且这会减少以确定AAA相对于第一个B ***的位置(我们知道的是A的数量) *** S)。

这样的事情应该有效,这简单地概括了这个想法。 (免责声明:这是未经测试的 - 可能很容易成为我忘记的边缘情况等等,但我非常有信心基本的想法是合理的。)

from math import factorial
from collections import Counter

def number_of_distinct_permutations(counts):
    f = factorial(sum(counts.values()))
    for letter, count in counts.items():
        f //= factorial(count)
    return f

def compute_index(word, index=0):
    if not word:
        return index + 1
    pending = Counter(word)
    head = word[0]
    for p in sorted(pending):
        if p < head:
            index += number_of_distinct_permutations(pending - Counter(p))
        if p == head:
            index += compute_index(word[1:])
    return index


test_data = {"ABAB": 2,
             "AAAB": 1,
             "BAAA": 4,
             "QUESTION": 24572,
             "BOOKKEEPER": 10743,
             "NONINTUITIVENESS": 8222334634}

print("word, reference, calculated")
for k,v in sorted(test_data.items()):
    print (k, v, compute_index(k))

产生

word, reference, calculated
AAAB 1 1
ABAB 2 2
BAAA 4 4
BOOKKEEPER 10743 10743
NONINTUITIVENESS 8222334634 8222334634
QUESTION 24572 24572

答案 1 :(得分:0)

这是DSM's solution的非反复变体(一般的想法完全是他/她):

from collections import Counter
from math import factorial

def number_of_distinct_permutations(counted):
    result = factorial(sum(counted))
    for each in counted:
        result //= factorial(each)
    return result

def anagram_number(iterable):
    elems = list(iterable)
    tally = Counter()
    index = 1
    while elems:
        current = elems.pop()
        tally[current] += 1
        for item in tally:
            if item < current:
                tally[item] -= 1
                index += number_of_distinct_permutations(tally.values())
                tally[item] += 1
    return index

答案 2 :(得分:0)

我昨天独立于这个论坛听到了这个问题。下面是C中的简单解决方案。它混合了一些Win32内容,但应该很容易转换为python(或其他)。它仅仅依赖于具有多重性m1 ... mk的字母的数量由多项系数(SUM(mi))!/ PRODUCT(mi!)给出的事实。它使用小写(a-z)并且格式粗糙,绝对没有错误控制。在这里它(multinom.exe)与multinom2.exe一起运行,它回答了相反的问题......给定字母和索引,找到字符串。以下是代码

multinom.exe问题 index = 24572

multinom2.exe noitseuq 24572 string = question

#include <Windows.h>
#include <stdio.h>

ULONGLONG fact( ULONGLONG n )
{
    if ( n == 0 )
        return 1;
    return n * fact(n-1);
}

ULONGLONG multinom( INT mults[] ) // = (SUM(M))! / PROD(M!)
{
    ULONGLONG n = 0;
    for ( INT i=0; i<26; i++ )
        n += mults[i];
    ULONGLONG result = fact(n);
    for ( INT i=0; i<26; i++ )
        if ( mults[i] )
            result /= fact(mults[i]);
    return result;
}

// uses a~z as alphabet; strings up to 20 chars; no safeguards or E/C whatsoever.
INT main ( INT argc, LPSTR* argv )
{
    ULONGLONG index = 1; // we'll add to this any earlier strings

    CHAR str[21];
    lstrcpy(str, argv[1]);

    INT mults[26] = {0}; // initialize multiplicities to zero

    for ( CHAR *p=str; *p != 0; p++ ) // set multiplicities that are non-zero
    {
        mults[*p - 'a'] += 1;   
    }

    for ( CHAR *p = str; *p != 0; p++ ) // iterate through the characters of str
    {
        for ( INT i=0; i < (*p - 'a'); i++ ) // check each character lexicographically before *p
        {
            if ( mults[i] ) // it's in the string; count (as earlier in the list) the strings that start with it
            {
                mults[i] -= 1;
                index += multinom(mults);
                mults[i] += 1;
            }
        }
        // At this point we've counted all the words that start with an earlier character.
        // Any remaining earlier words must match up to this point.  So ...
        mults[*p - 'a'] -= 1; // p will be incremented so, in effect, forget this character and move on
    }
    printf("index = %I64u\n", index);
    return 0;
}