2

I'm looking for the equation to determine the index of a permutation with repetition with known parameters.

For example: A total of $9$ values, $4$ A's and $5$ B's Gives a total of $126$ permutations with repetition. $$\frac{9!}{4! \cdot 5!} = 126$$

The Zero-based lexicographical order goes from 0 = AAAABBBBB to 125 = BBBBBAAAA This data set is trivial enough that I just generated all the values with code, but large data sets are impractical. I know that index 76 = BABABABAB since I have a list of answers, but I don't want to generate a partial or a full list.

How do I direclty convert any sequence such as BABABABAB to the permutation with repetition index? How do I direclty do the reverse and convert the permutation with repetition index back to the sequence?

I'm looking for the equations / methods to use in a non-trivial example.

Lexicographical order is prefered, but not required as long as the method can convert in both directions (Sequence => Index and Index => Sequence).

Vepir
  • 12,516

1 Answers1

6

Forward conversion was explained in "Lexicographical rank of a string with duplicate characters". In short, I'm referencing the other answer from that question:

If the $i$th character is repeated $n_i$ times, then the total number of permutations is given by:

$$ \frac{(n_1+n_2+\dots+n_m)!}{n_1!\cdot n_2! \cdot \space ... \space \cdot n_m!} $$

We can at $k$th step consider the $k$th character of the given string and fix all characters before it. Now, if you replace this character with any of the preceding characters, then each of the possible permutations will precede the given permutation.

We can calculate the number of such permutations with the given formula. Summing these calculations over all steps will give the total number of preceding permutations to the given permutation, which is the number we are after.

I've implemented this in python and tested it on your example: (proof of concept)

from math import factorial
from functools import reduce
from collections import Counter

def lexicographical_index(string): [rank, l, freqs] = [0, len(string), Counter(string)] min_ord = min([ord(key) for key in freqs.keys()]) for n in range(l): fsum = sum([freqs[chr(j)] for j in range(min_ord,ord(string[n]))]) fprod = reduce(lambda x,y: yx, [factorial(v) for v in freqs.values()]) freqs[string[n]] -= 1; rank += ((fsum factorial(l-n-1)) // fprod) return rank

print(lexicographical_index("babababab"))

which returns the expected result:

76

and should run in $O(m\cdot n)$ where $m$ is the number of unique chars among the $n$ chars.

The backward conversion uses the same idea. This time around, we are fixing characters from smallest to largest and counting the possible permutations until the count exceeds our index, until we fix (find) every character.

This was additionally explained and implemented in:

Vepir
  • 12,516
  • Note that if all characters of your string are distinct (no duplicates), then we are permuting sets instead of multisets and the solution can be more efficient. See How to find the next higher permutation out of a fixed group of digits? where they discuss the Factorial number system and the Lehmer code. – Vepir Aug 25 '20 at 20:21
  • Your code looks very elegant, but I'm not familiar with Python. Are you able to show this in VB.Net or C++ or even add a few comments. I assume it is doing the same thing as the link you provided but looks more robust as it isn't using a do while loop. – user819146 Aug 25 '20 at 21:31
  • @cadthat It works the same as the C++ code that is linked in that answer I linked (You can see the C++ code at the end of this answer). It's just that python has "list comprehension" and ready things like "sum" and "reduce", so those for and while loops are hidden in one or two lines. (The only difference I can think of is that I use a dictionary instead of an array, for the frequencies of characters.) – Vepir Aug 25 '20 at 21:40
  • @cadthat I added comments to my new version of the code that you can see here on pastebin which has syntax highlight. :) – Vepir Aug 25 '20 at 21:57
  • Your help has been exceptional. Thank you. I've been creating my code in vb.net and tested it on the 126 permutation with repetition example but ran into a problem with 91 of the 126 that they are low by 1 to 5. Would you mind checking index 11 aabbaabbb is low by 1 and index 125 bbbbbaaaa is low by 5; . I want to know that I have a bug to fix for sure. Index 76 comes out correct just like expected. – user819146 Aug 26 '20 at 01:36
  • @cadthat You can run my code and compare the results. It works fine for me, you must have a bug in your vb.net implementation. Are you sure your factorials aren't overflowing and your division is not rounding anything. – Vepir Aug 26 '20 at 01:38
  • Thank you, I'll chase it down. – user819146 Aug 26 '20 at 01:47
  • That was awsome that I could run your python code online! I found I had an order of operation error in the formula part. I needed to multiply by fsum before integer dividing. – user819146 Aug 26 '20 at 02:00
  • @cadthat Yes, online interpreters and compilers exist for all common programming languages. I'm glad you sorted it out, this was a fun problem for me :) – Vepir Aug 26 '20 at 02:06
  • @Vepir What if we want all the permutations with limited repetitions? Like all the variations between 0000000000 and 9999999999 with 5 repetitions in each variation? This is the question that I've asked: https://math.stackexchange.com/questions/4247472/permutations-with-global-limited-repetition-without-the-need-for-providing-digit – Eftekhari Sep 12 '21 at 17:15
  • @Eftekhari I have commented on your question. – Vepir Sep 12 '21 at 18:37
  • @Vepir Thank you... – Eftekhari Sep 12 '21 at 18:42