Python-функция для размещения строки в алфавитном списке перестановок ее символов - PullRequest
0 голосов
/ 26 августа 2018

Я написал код для решения следующей проблемы, однако в последних двух тестах он не работает. Моя логика, используемая для решения проблемы, кажется разумной, и даже после того, как коллега рассмотрел ее, мы оба не можем понять, почему она будет работать для первых 8 тестовых случаев, но не для двух последних (которые были сгенерированы случайным образом).

Проблема:

Если задана строка, вернуть позицию введенной строки в отсортированный по алфавиту список всех возможных перестановок символы в этой строке. Например, перестановки ABAB являются [AABB, ABAB, ABBA, BAAB, BABA, BBAA], где позиция ABAB в этом список 2.

Логика, которую я использовал для решения проблемы:

Для больших входных данных невозможно (неэффективно) сформировать список перестановок, поэтому смысл состоит в том, чтобы найти позицию без генерации алфавитного списка. Это можно сделать, найдя частоту символов. В приведенном выше примере первый символ в ABAB - это A, поэтому до = 0 и после = .5, а между = 6, поэтому вы уменьшаете max на .5 * 6, что равно 3, для minn из 1 и maxx из 3 , чтобы оставить только [AABB, ABAB, ABBA], химическая завивка с A в качестве первого символа! Тогда оставленные символы - БАБ. minn = 1 и maxx = 3, а между = 3. Таким образом, для B значение before было бы .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?

Ответы [ 2 ]

0 голосов
/ 26 августа 2018

Вы можете использовать логику, которая требует только целочисленной арифметики. Сначала создайте лексикографически первую перестановку:

BOOKKEEPER  ->  BEEEKKOOPR

Затем для каждой буквы вы можете рассчитать, сколько уникальных перестановок потребовалось, чтобы переместить ее на свое место. Поскольку первая буква B уже на месте, мы можем ее проигнорировать и посмотреть остальные буквы:

B EEEKKOOPR  (first)
B OOKKEEPER  (target)

Чтобы узнать, сколько нужно перестановок, чтобы вывести О на передний план, мы рассчитываем, сколько уникальных перестановок существует с Е впереди, затем с К впереди:

E+EEKKOOPR -> 8! / (2! * 2! * 2!) = 40320 /  8 = 5040
K+EEEKOOPR -> 8! / (3! * 2!)      = 40320 / 12 = 3360

Где 8 - количество переставляемых букв, а 2 и 3 - количество кратных букв. Итак, после 8400 перестановок мы находимся на:

BO EEEKKOPR

Теперь мы снова подсчитаем, сколько нужно перестановок, чтобы вывести вторую букву О:

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

0 голосов
/ 26 августа 2018

Как указывает @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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...