Я написал код для решения следующей проблемы, однако он не работает в двух последних тестовых случаях. Моя логика, используемая для решения проблемы, кажется звуковой, и даже после того, как ее коллега рассмотрит ее, мы оба не можем понять, почему она будет работать для первых 8 тестовых случаев, но не для последних двух (которые были случайным образом сгенерированы).
С учетом строки верните позицию введенной строки в отсортированном в алфавитном порядке списке всех возможных перестановок символов в этой строке. Например, перестановки ABAB - это [AABB, ABAB, ABBA, BAAB, BABA, BBAA], где позиция ABAB в этом списке равна 2.
Для больших входов невозможно (неэффективно) генерировать список перестановок, поэтому точка должна найти позицию без генерации алфавитного списка. Это можно сделать, найдя частоту символов. Для вышеприведенного примера первый charcater в ABAB равен A, поэтому до = 0 и после =.5 и между = 6, поэтому вы уменьшаете max на 0,5 * 6, что равно 3, для minn 1 и maxx из 3, оставить только [AABB, ABAB, ABBA], пермы с A в качестве первого символа! Затем оставшиеся символы - BAB. minn = 1 и maxx = 3, а между = 3. Итак, для B раньше было бы.33, а после было бы 0, поэтому увеличьте minn на 3 *.33 для minn 2 и maxx из 3, что равно [ABAB, ABBA], которые имеют AB как первые два символа! Продолжайте делать это для каждого персонажа, и он найдет вход в списке.
## Imports
import operator
from collections import Counter
from math import factorial
from functools import reduce
## Main function, returns list position
def listPosition(word):
#turns string into list of numbers, A being 1, B being 2, and so on
val = [ord(char) - 96 for char in word.lower()]
#the result has to be between 1 and the number of permutations
minn = 1
maxx = npermutations(word)
#so we just increase the min and decrease the max based on the sum of freq
#of the characters less than and greater than each character
for indx in range(len(word)):
between = (maxx+1-minn)
before,after = sumfreq(val[indx:],val[indx])
minn = minn + int(round((between * before),0))
maxx = maxx - int(between * after)
return maxx #or minn, doesn't matter. they're equal at this point
## returns the number of permutations for the string (this works)
def npermutations(word):
num = factorial(len(word))
mults = Counter(word).values()
den = reduce(operator.mul, (factorial(v) for v in mults), 1)
return int(num / den)
## returns frequency as a percent for the character in the list of chars
def frequency(val,value):
f = [val.count(i)/len(val) for i in val]
indx = val.index(value)
return f[indx]
#returns sum of frequencies for all chars < (before) and > (after) the said char
def sumfreq(val,value):
before = [frequency(val,i) for i in [i for i in set(val) if i < value]]
after = [frequency(val,i) for i in [i for i in set(val) if i > value]]
return sum(before),sum(after)
tests= ['A','ABAB','AAAB','BAAA','QUESTION','BOOKKEEPER','ABCABC','IMMUNOELECTROPHORETICALLY','ERATXOVFEXRCVW','GIZVEMHQWRLTBGESTZAHMHFBL']
print(listPosition(tests[0]),"should equal 1")
print(listPosition(tests[1]),"should equal 2")
print(listPosition(tests[2]),"should equal 1")
print(listPosition(tests[3]),"should equal 4")
print(listPosition(tests[4]),"should equal 24572")
print(listPosition(tests[5]),"should equal 10743")
print(listPosition(tests[6]),"should equal 13")
print(listPosition(tests[7]),"should equal 718393983731145698173")
print(listPosition(tests[8]),"should equal 1083087583") #off by one digit?
print(listPosition(tests[9]),"should equal 5587060423395426613071") #off by a lot?
Как указывает @rici, это ошибка с плавающей запятой (см. Разбивается математика с плавающей запятой?). К счастью, у python есть fractions
.
Несколько разумных применений fractions.Fraction
исправляет проблему, не меняя тело кода, например:
from fractions import Fraction
...
## returns the number of permutations for the string (this works)
def npermutations(word):
num = factorial(len(word))
mults = Counter(word).values()
den = reduce(operator.mul, (factorial(v) for v in mults), 1)
return int(Fraction(num, den))
## returns frequency as a percent for the character in the list of chars
def frequency(val,value):
f = [Fraction(val.count(i),len(val)) for i in val]
indx = val.index(value)
return f[indx]
...
In []:
print(listPosition(tests[0]),"should equal 1")
print(listPosition(tests[1]),"should equal 2")
print(listPosition(tests[2]),"should equal 1")
print(listPosition(tests[3]),"should equal 4")
print(listPosition(tests[4]),"should equal 24572")
print(listPosition(tests[5]),"should equal 10743")
print(listPosition(tests[6]),"should equal 13")
print(listPosition(tests[7]),"should equal 718393983731145698173")
print(listPosition(tests[8]),"should equal 1083087583")
print(listPosition(tests[9]),"should equal 5587060423395426613071")
Out[]:
1 should equal 1
2 should equal 2
1 should equal 1
4 should equal 4
24572 should equal 24572
10743 should equal 10743
13 should equal 13
718393983731145698173 should equal 718393983731145698173
1083087583 should equal 1083087583
5587060423395426613071 should equal 5587060423395426613071
Основываясь на @m69 отличное объяснение здесь намного проще:
from math import factorial
from collections import Counter
from functools import reduce
from operator import mul
def position(word):
charset = Counter(word)
pos = 1 # Per OP 1 index
for letter in word:
chars = sorted(charset)
for char in chars[:chars.index(letter)]:
ns = Counter(charset) - Counter([char])
pos += factorial(sum(ns.values())) // reduce(mul, map(factorial, ns.values()))
charset -= Counter([letter])
return pos
Это дает те же результаты, что и выше:
In []:
tests = ['A', 'ABAB', 'AAAB', 'BAAA', 'QUESTION', 'BOOKKEEPER', 'ABCABC',
'IMMUNOELECTROPHORETICALLY', 'ERATXOVFEXRCVW', 'GIZVEMHQWRLTBGESTZAHMHFBL']
print(position(tests[0]),"should equal 1")
print(position(tests[1]),"should equal 2")
print(position(tests[2]),"should equal 1")
print(position(tests[3]),"should equal 4")
print(position(tests[4]),"should equal 24572")
print(position(tests[5]),"should equal 10743")
print(position(tests[6]),"should equal 13")
print(position(tests[7]),"should equal 718393983731145698173")
print(position(tests[8]),"should equal 1083087583")
print(position(tests[9]),"should equal 5587060423395426613071")
Out[]:
1 should equal 1
2 should equal 2
1 should equal 1
4 should equal 4
24572 should equal 24572
10743 should equal 10743
13 should equal 13
718393983731145698173 should equal 718393983731145698173
1083087583 should equal 1083087583
5587060423395426613071 should equal 5587060423395426613071
Вы можете использовать логику, которая требует целочисленной арифметики. Сначала создайте лексикографически первую перестановку:
BOOKKEEPER -> BEEEKKOOPR
Затем для каждой буквы вы можете подсчитать, сколько уникальных перестановок потребовалось, чтобы переместить их на свое место. Поскольку первая буква B уже установлена, мы можем ее игнорировать и смотреть на остальные буквы:
B EEEKKOOPR (first)
B OOKKEEPER (target)
Чтобы узнать, сколько перестановок требуется для вывода O на фронт, мы вычислим, сколько уникальных перестановок есть с E впереди, затем с K впереди:
E+EEKKOOPR -> 8! / (2! * 2! * 2!) = 40320 / 8 = 5040
K+EEEKOOPR -> 8! / (3! * 2!) = 40320 / 12 = 3360
Где 8 - количество букв, подлежащих перестановке, а 2 и 3 - количество кратных букв. Итак, после 8400 перестановок мы находимся по адресу:
BO EEEKKOPR
Теперь, опять же, мы вычислим, сколько перестановок требуется, чтобы довести второй O до фронта:
E+EEKKOPR -> 7! / (2! * 2!) = 5040 / 4 = 1260
K+EEEKOPR -> 7! / (3!) = 5040 / 6 = 840
Итак, после 10500 перестановок мы находимся по адресу:
BOO EEEKKPR
Затем мы вычислим, сколько перестановок требуется, чтобы довести K до фронта:
E+EEKKPR -> 6! / (2! * 2!) = 720 / 4 = 180
Итак, после 10680 перестановок мы находимся по адресу:
BOOK EEEKPR
Затем мы вычислим, сколько перестановок требуется, чтобы довести второй К до фронта:
E+EEKPR -> 5! / 2! = 120 / 2 = 60
Итак, после 10740 перестановок мы находимся по адресу:
BOOKK EEEPR
Следующие две буквы уже на месте, поэтому мы можем перейти к:
BOOKKEE EPR
Затем мы вычисляем, сколько перестановок требуется, чтобы получить P спереди:
E+PR -> 2! = 2
Итак, после 10742 перестановок мы находимся по адресу:
BOOKKEEP ER
И последние две буквы тоже уже в порядке, поэтому ответ 10743 (добавьте 1, потому что запрашивается индекс на основе 1).
AABB
появится 4 раза, если вы переставляетеABAB