I solved a puzzle but need to optimize my solution. The puzzle says that I am to take a string S, find all permutations of its characters, sort my results, and then return the one-based index of where S appears in that list.
For example, the string 'bac' appears in the 3rd position in the sorted list of its own permutations: ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'].
My problem is that the puzzle limits my execution time to 500ms. One of the test cases passed "BOOKKEEPER" as an input, which takes ~4.2s for me to complete.
I took a (possibly naive) dynamic programming approach using memoization using a dict keyed by one particular permutation of some character set, but that's not enough.
What is my bottleneck?
I'm profiling in the meantime to see if I can answer my own question, but I invite those who see the problem outright to help me understand how I slowed this down.
EDIT: My solution appears to outperform itertools.permutations. 10+ seconds for input "QUESTION". But to be fair, this includes time printing so this might not be a fair comparison. Even so, I'd rather submit a handcoded solution with competitive performance knowing why mine was worse than to opt for a module.
memo = {}
def hash(word):
    return ''.join(sorted(word))
def memoize(word, perms):
    memo[hash(word)] = perms
    return perms
def permutations(word, prefix = None):
    """Return list of all possible permutatons of given characters"""
    H = hash(word)
    if H in memo:
        return [s if prefix is None else prefix + s for s in memo[H]]
    L = len(word)
    if L == 1:
        return [word] if prefix is None else [prefix + word]
    elif L == 2:
        a = word[0] + word[1]
        b = word[1] + word[0]
        memoize(word, [a, b])
        if prefix is not None:
            a = prefix + a
            b = prefix + b
        return [a, b]
    perms = []
    for i in range(len(word)):
        perms = perms + permutations(word[:i] + word[i+1:], word[i])
    memoize(word, perms)
    return [prefix + s for s in perms] if prefix is not None else perms
def listPosition(word):
  """Return the anagram list position of the word"""
  return sorted(list(set(permutations(word)))).index(word) + 1
print listPosition('AANZ')
 
     
    