Допустим, у нас есть строка «ABCD», и мы хотим получить букву в i-й позиции из n-й перестановки строки.
В этом примере я знаю, что есть factorial (4) = 24 перестановки, и список можно легко получить с помощью itertools.permutations, что даст следующее:
['ABCD', 'ABDC', 'ACBD', 'ACDB', 'ADBC', 'ADCB', 'BACD', 'BADC', 'BCAD', 'BCDA', 'BDAC', 'BDCA', 'CABD', 'CADB', 'CBAD', 'CBDA', 'CDAB', 'CDBA', 'DABC', 'DACB', 'DBAC', 'DBCA', 'DCAB', 'DCBA']
Итак, функция f, которую я ищу, должна вернуть:
f(0, n) == ['A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'C', 'C', 'C', 'C', 'C', 'C', 'D', 'D', 'D', 'D', 'D', 'D'][n]
f(1, n) == ['B', 'B', 'C', 'C', 'D', 'D', 'A', 'A', 'C', 'C', 'D', 'D', 'A', 'A', 'B', 'B', 'D', 'D', 'A', 'A', 'B', 'B', 'C', 'C'][n]
f(2, n) == ['C', 'D', 'B', 'D', 'B', 'C', 'C', 'D', 'A', 'D', 'A', 'C', 'B', 'D', 'A', 'D', 'A', 'B', 'B', 'C', 'A', 'C', 'A', 'B'][n]
f(3, n) == ['D', 'C', 'D', 'B', 'C', 'B', 'D', 'C', 'D', 'A', 'C', 'A', 'D', 'B', 'D', 'A', 'B', 'A', 'C', 'B', 'C', 'A', 'B', 'A'][n]
Это довольно просто для i == 0, у нас есть f (0, n) == "ABCD" [n // 6], но поиск шаблона, когда i увеличивается, становится все более и более сложным.
Меня совершенно не волнует, как упорядочены перестановки, поэтому, возможно, можно легко найти общий шаблон для всех значений i ...
Я планирую использовать это с набором из 256 элементов и факториальных (256) перестановок, поэтому вычисление перестановок не вариант.
Обновлено: у меня уже есть функция для этого, но она слишком медленная, и мне было интересно, можно ли найти какой-либо эквивалентный результат с простой формулой, используя, например, побитовые операции ...
Edit-2: Вот текущее лучшее решение благодаря @rici:
f = [factorial(i) for i in range(256)]
def _getElt(k, i):
"""
Returns the <i>th element of the <k>th permutation of 0..255
"""
table = list(range(256))
for j in range(255, 254-i, -1):
r, k = divmod(k, f[j])
perm[j], perm[r] = perm[r], perm[j]
return perm[255 - i]
Edit-3: Вот другой подход, использующий полиномиальные приближения для воссоздания перестановок, поэтому другая формулировка проблемы могла бы быть «как воссоздать i-й коэффициент полинома для n-й перестановки?».
Это список из n, перестановки, коэффициента полинома (и, наконец, перестановки, перестроенной из коэффициентов полинома) для N = 4:
0 [0, 1, 2, 3] [Fraction(0, 1), Fraction(1, 1), Fraction(0, 1), Fraction(0, 1)] [0, 1, 2, 3]
1 [0, 1, 3, 2] [Fraction(0, 1), Fraction(-3, 4), Fraction(5, 2), Fraction(-2, 3)] [0, 1, 3, 2]
2 [0, 2, 1, 3] [Fraction(0, 1), Fraction(11, 2), Fraction(-9, 2), Fraction(1, 1)] [0, 2, 1, 3]
3 [0, 2, 3, 1] [Fraction(0, 1), Fraction(7, 4), Fraction(1, 2), Fraction(-1, 3)] [0, 2, 3, 1]
4 [0, 3, 1, 2] [Fraction(0, 1), Fraction(33, 4), Fraction(-13, 2), Fraction(4, 3)] [0, 3, 1, 2]
5 [0, 3, 2, 1] [Fraction(0, 1), Fraction(19, 3), Fraction(-4, 1), Fraction(2, 3)] [0, 3, 2, 1]
6 [1, 0, 2, 3] [Fraction(1, 1), Fraction(-15, 4), Fraction(7, 2), Fraction(-2, 3)] [1, 0, 2, 3]
7 [1, 0, 3, 2] [Fraction(1, 1), Fraction(-17, 3), Fraction(6, 1), Fraction(-4, 3)] [1, 0, 3, 2]
8 [1, 2, 0, 3] [Fraction(1, 1), Fraction(21, 4), Fraction(-11, 2), Fraction(4, 3)] [1, 2, 0, 3]
9 [1, 2, 3, 0] [Fraction(1, 1), Fraction(-1, 3), Fraction(2, 1), Fraction(-2, 3)] [1, 2, 3, 0]
10 [1, 3, 0, 2] [Fraction(1, 1), Fraction(31, 4), Fraction(-15, 2), Fraction(5, 3)] [1, 3, 0, 2]
11 [1, 3, 2, 0] [Fraction(1, 1), Fraction(17, 4), Fraction(-5, 2), Fraction(1, 3)] [1, 3, 2, 0]
12 [2, 0, 1, 3] [Fraction(2, 1), Fraction(-17, 4), Fraction(5, 2), Fraction(-1, 3)] [2, 0, 1, 3]
13 [2, 0, 3, 1] [Fraction(2, 1), Fraction(-31, 4), Fraction(15, 2), Fraction(-5, 3)] [2, 0, 3, 1]
14 [2, 1, 0, 3] [Fraction(2, 1), Fraction(1, 3), Fraction(-2, 1), Fraction(2, 3)] [2, 1, 0, 3]
15 [2, 1, 3, 0] [Fraction(2, 1), Fraction(-21, 4), Fraction(11, 2), Fraction(-4, 3)] [2, 1, 3, 0]
16 [2, 3, 0, 1] [Fraction(2, 1), Fraction(17, 3), Fraction(-6, 1), Fraction(4, 3)] [2, 3, 0, 1]
17 [2, 3, 1, 0] [Fraction(2, 1), Fraction(15, 4), Fraction(-7, 2), Fraction(2, 3)] [2, 3, 1, 0]
18 [3, 0, 1, 2] [Fraction(3, 1), Fraction(-19, 3), Fraction(4, 1), Fraction(-2, 3)] [3, 0, 1, 2]
19 [3, 0, 2, 1] [Fraction(3, 1), Fraction(-33, 4), Fraction(13, 2), Fraction(-4, 3)] [3, 0, 2, 1]
20 [3, 1, 0, 2] [Fraction(3, 1), Fraction(-7, 4), Fraction(-1, 2), Fraction(1, 3)] [3, 1, 0, 2]
21 [3, 1, 2, 0] [Fraction(3, 1), Fraction(-11, 2), Fraction(9, 2), Fraction(-1, 1)] [3, 1, 2, 0]
22 [3, 2, 0, 1] [Fraction(3, 1), Fraction(3, 4), Fraction(-5, 2), Fraction(2, 3)] [3, 2, 0, 1]
23 [3, 2, 1, 0] [Fraction(3, 1), Fraction(-1, 1), Fraction(0, 1), Fraction(0, 1)] [3, 2, 1, 0]
Мы ясно видим, что существует симметрия: coefs [i] = [3 - coefs [23-i] [0]] + [-c for c in coefs [23-i] [1:]], так что это способ исследовать, но я понятия не имею, что это возможно.
Попробуйте не вычислять factorial(f) по три раза за каждый шаг? Если вы установите некоторую переменную fact = factorial(f) в начале, то вы можете просто разделить ее на f на первом этапе, затем на f-1, затем на f-2 и так далее.
@Nelfeal да, это был просто пример, в моих исходных функциях факториалы вычисляются заранее ... Я просто хочу избавиться от всего цикла while.
Я верю, что ты не можешь






Вы можете найти n-ю перестановку, повторяя евклидово деление (частное и остаток, иначе divmod) и отслеживая, какие буквы вы выбираете. Затем вы можете выбрать i-ю букву из этой перестановки.
Пусть S будет копией начальной строки, L - длиной этой строки, а P - количеством перестановок (L!). T будет n-й перестановкой S, построенной шаг за шагом.
Пример: S = "ABCD", L = 4 и P = 24. Для примера возьмем n = 15. T в конце должен быть "CBDA".
Установите P = P/L. Вычислите divmod(n, P), пусть q будет частным (n/P), а r остатком (n%P). Удалите q-ю букву из S и добавьте ее в T. Установить n = r, уменьшить L и повторять до L = 0.
Пример:
1) P = 24/4 = 6, q = 15/6 = 2, r = 15%6 = 3, S = "ABD", T = "C", n = r = 3, L = 3.
2) P = 6/3 = 2, q = 3/2 = 1, r = 3%2 = 1, S = "AD", T = "CB", n = r = 1, L = 2.
3) P = 2/2 = 1, q = 1/1 = 1, r = 1%1 = 0, S = "A", T = "CBD", n = r = 0, L = 1.
4) P = 1/1 = 1, q = 0/1 = 0, r = 0%1 = 0, S = "", T = "CBDA", n = r = 0, L = 0.
Поскольку вам нужна только буква i, вы можете остановиться, когда длина T равна i+1, и взять эту последнюю букву.
Я не буду пытаться кодировать это на Python, потому что прошло слишком много времени с тех пор, как я прикасался к Python, но вот демонстрация на C++.
Большое спасибо за ваш ответ. У меня уже есть такая функция в python, мне было интересно, можно ли сделать прямую эквивалентность ...
Но это прямая эквивалентность. Он имеет только шаги L и может быть отменен (также в шагах L). Я почти уверен, что вы не получите ничего более простого.
Простите за мой Python. Идея в том, что буква в каждой позиции повторяет (number_of_pos - pos)! раз.
Например. ABCD, буква в первой позиции будет повторять 3! раз. Итак, разделив n на этот факториал, мы теперь, какая буква стоит на первой позиции. Чтобы найти букву во второй позиции, нам нужно удалить эту букву из набора (вместо этого назначить 0) и обновить n с помощью n - n % 3!, чтобы теперь индекс буквы во второй позиции. Применяя этот индекс, мы должны позаботиться о том, чтобы не считать буквы, стоящие на первой позиции.
Сложность - O(kPosCount * kPosCount).
#include <array>
#include <iostream>
const int kPosCount = 4;
const std::array<int, kPosCount> factorial = { 1,1,2,6 };
const std::array<int, kPosCount> kLetters = { 'A', 'B', 'C', 'D' };
char f(int pos, int n) {
assert(pos < kPosCount);
std::array<int, kPosCount> letters = kLetters;
int res = 0;
for (int i = 0; i <= pos; ++i) {
const int letter = n / factorial[kPosCount - i - 1];
int j = 0;
for (int stillThere = 0; j < kPosCount; ++j) {
if (letters[j] != 0) {
if (stillThere == letter) {
break;
}
++stillThere;
}
}
letters[j] = 0;
res = j;
n %= factorial[kPosCount - i - 1];
}
return kLetters[res];
}
int main() {
const int kMaxN = factorial.back() * kPosCount;
for (int i = 0; i < kPosCount; ++i) {
for (int j = 0; j < kMaxN; ++j) {
std::cout << f(i, j) << " ";
}
std::cout << std::endl;
}
}
Вот версия вашей функции с другим порядком перестановки. Я не устанавливал здесь размер перестановки 256, поэтому вы должны указать его в качестве первого аргумента.
def elt(n,k,i):
"""Returns element i of permutation k from permutations of length n"""
perm = [*range(n)]
for j in range(i+1):
k, r = divmod(k, n-j)
perm[j], perm[j+r] = perm[j+r], perm[j]
return perm[i]
Это имеет два отличия от вашего кода.
Во-первых, цифры вырезаются из индекса справа налево, что означает, что все модули bignum divmods являются модулями divmods с небольшим делимым. Я думал, что это будет быстрее, чем использование больших дивидендов, но оказалось, что это значительно медленнее. Однако он позволяет избежать предварительного вычисления факториалов.
Во-вторых, вместо того, чтобы выполнять серию pop из середины таблицы, которая будет иметь квадратичную сложность, потому что операция pop - O (n), я просто меняю местами каждый элемент с некоторым прямым элементом. Это значительно быстрее, хотя большая арифметика доминирует в вычислениях.
Фактически, elt(n,k,i) выполняет первые шаги i в тасовке Фишера-Йетса range(n), используя разложение k по смешанной основе в качестве случайных значений при случайном тасовании. Поскольку перетасовка F-Y дает разные результаты для каждой возможной последовательности случайных значений, а разложение k на смешанное основание дает разную последовательность для каждого другого значения k, будут сгенерированы все перестановки.
Вот как выглядит порядок для n = 4:
>>> print('\n'.join(' '.join(str(elt(4, k, i)) for i in range(4))
for k in range(factorial(4))))
0 1 2 3
1 0 2 3
2 1 0 3
3 1 2 0
0 2 1 3
1 2 0 3
2 0 1 3
3 2 1 0
0 3 2 1
1 3 2 0
2 3 0 1
3 0 2 1
0 1 3 2
1 0 3 2
2 1 3 0
3 1 0 2
0 2 3 1
1 2 3 0
2 0 3 1
3 2 0 1
0 3 1 2
1 3 0 2
2 3 1 0
3 0 1 2
Чтобы действительно ускорить работу функции, я повторно ввел предварительно вычисленные факториалы (как локальную переменную, которая расширяется по мере необходимости). Я также микрооптимизировал внутренний цикл, удалив как можно больше арифметических операций, что означало выполнение перетасовки F-Y сзади вперед. Это приводит к еще одному порядку перестановки; см. ниже пример.
Окончательная оптимизированная функция примерно на 15% быстрее, чем версия, о которой идет речь (то есть на выполнение простого теста требуется около 85% времени).
def elt_opt(n, k, i, fact=[1]):
"""Returns element i of permutation k from permutations of length n.
Let the last argument default.
"""
while len(fact) < n: fact.append(fact[-1]*len(fact))
perm = list(range(n))
for j in range(n-1, n-2-i, -1):
r, k = divmod(k, fact[j])
perm[j], perm[r] = perm[r], perm[j]
return perm[n-i-1]
Порядок перестановки:
>>> print('\n'.join(' '.join(str(elt_opt(4, k, i)) for i in range(4))
for k in range(factorial(4))))
0 3 2 1
0 3 1 2
0 1 3 2
0 1 2 3
0 2 3 1
0 2 1 3
1 0 2 3
1 0 3 2
1 3 0 2
1 3 2 0
1 2 0 3
1 2 3 0
2 0 3 1
2 0 1 3
2 1 0 3
2 1 3 0
2 3 0 1
2 3 1 0
3 0 2 1
3 0 1 2
3 1 0 2
3 1 2 0
3 2 0 1
3 2 1 0
Большое спасибо, материал о Фишере-Йейтсе очень интересен, а также генерирует перестановки без вычисления факториалов! Но после тестирования я обнаружил, что это решение немного медленнее моего. Понятия не имею, почему, возможно, Python 3.7 лучше справляется со списком .pop ()
@fbparis: оказывается, я ошибался насчет разделения; Python на самом деле примерно на 60% быстрее делит два больших числа с малым частным, чем делит большое число на небольшое делимое. Это подавляет ускорение обмена.
@fbparis: Я добавил самую оптимизированную версию, которую смог придумать. Но он не намного быстрее твоего.
хорошо сделано (я приблизился к увеличению скорости на 10%), но он все еще не подходит для предполагаемого использования, я думаю, мне нужно что-то <O (n). Я сильно сомневаюсь, что это возможно, но я буду продолжать немного пробовать новые вещи (в настоящее время я тестирую вещи с помощью интерполяции Лагранжа ...)
@fbparis: я не верю, что сублинейная существует, но вы могли бы как-то переформулировать свою проблему. Например, если вам нужно более одного элемента из одной и той же перестановки, все, кроме одного, будут бесплатными. Так что это может очень помочь
есть другой способ улучшить его, используя симметрию перестановок, чтобы мы никогда не зацикливали больше, чем N // 2
Позвольте нам продолжить обсуждение в чате.
@fbparis. Конечно, но бигнумы больше в начале цикла, поэтому первая итерация будет самой медленной. Тем не менее, мне трудно представить себе вариант использования, в котором вам когда-либо понадобится только один элемент назначенной перестановки.
Так в чем конкретно ваш вопрос?