import itertools
import math
import random
def rank(p: list[int], elements: set[int]) -> int:
"""Rank a permutation of k out of n things.
Args:
p: The permutation to rank
elements: The elements to choose from
Returns:
The rank of the permutation within all choices of
k out of n things
"""
k = len(p)
if k == 1:
return len(list(e for e in elements if e < p[0]))
head, tail = p[0], p[1:]
n = len(elements) - 1
num_preceding = len([e for e in elements if e < head])
preceding = num_preceding * (
math.factorial(n)
/ math.factorial(n - k + 1)
)
return int(preceding) + rank(tail, elements - {head})
def _update_p(p: list[int], i: int) -> None:
"""Update a candidate unranked permutation
Args:
p: The permutation to update
i: The index of the leftmost element to attempt to
increment
Returns:
None
"""
p[i] += 1
prev = p[:i]
while p[i] in prev:
p[i] += 1
for j in range(i + 1, len(p)):
p[j] = 0
prev = p[:j]
while p[j] in prev:
p[j] += 1
def unrank(index, k, n):
"""Unrank a permutation of k out of n things.
Args:
index: The rank of the permutation to unrank
k: The cardinality of the permutation
n: The size of the set the permutation is chosen
from, which is enumerated consecutively from zero
Returns:
The unranked permutation
"""
elements = sorted(range(n))
p = elements[:k]
for i in range(k):
if p[i] == n - 1:
continue
while True:
r = rank(p, set(elements))
if r > index:
break
prev = p[:]
_update_p(p, i)
p = prev
return p
# Test rank and unrank by checking reversibility
num_tests = 1
max_n = 800
max_k = 24
for _ in range(num_tests):
n = random.randint(5, max_n)
k = random.randint(2, min(max_k, n))
p = random.sample(range(n), k)
elements = set(range(n))
rank_p = rank(p, elements)
unranked = unrank(rank_p, k, n)
if unranked != p:
print((p, n, rank_p, unranked, rank(unranked, elements)))
break
print('Done.')
aW1wb3J0IGl0ZXJ0b29scwppbXBvcnQgbWF0aAppbXBvcnQgcmFuZG9tCgoKZGVmIHJhbmsocDogbGlzdFtpbnRdLCBlbGVtZW50czogc2V0W2ludF0pIC0+IGludDoKICAiIiJSYW5rIGEgcGVybXV0YXRpb24gb2YgayBvdXQgb2YgbiB0aGluZ3MuCgogIEFyZ3M6CiAgICBwOiBUaGUgcGVybXV0YXRpb24gdG8gcmFuawogICAgZWxlbWVudHM6IFRoZSBlbGVtZW50cyB0byBjaG9vc2UgZnJvbQoKICBSZXR1cm5zOgogICAgVGhlIHJhbmsgb2YgdGhlIHBlcm11dGF0aW9uIHdpdGhpbiBhbGwgY2hvaWNlcyBvZgogICAgICBrIG91dCBvZiBuIHRoaW5ncwogICIiIgoKICBrID0gbGVuKHApCgogIGlmIGsgPT0gMToKICAgIHJldHVybiBsZW4obGlzdChlIGZvciBlIGluIGVsZW1lbnRzIGlmIGUgPCBwWzBdKSkKCiAgaGVhZCwgdGFpbCA9IHBbMF0sIHBbMTpdCgogIG4gPSBsZW4oZWxlbWVudHMpIC0gMQogIG51bV9wcmVjZWRpbmcgPSBsZW4oW2UgZm9yIGUgaW4gZWxlbWVudHMgaWYgZSA8IGhlYWRdKQoKICBwcmVjZWRpbmcgPSBudW1fcHJlY2VkaW5nICogKAogICAgICBtYXRoLmZhY3RvcmlhbChuKQogICAgICAvIG1hdGguZmFjdG9yaWFsKG4gLSBrICsgMSkKICApCgogIHJldHVybiBpbnQocHJlY2VkaW5nKSArIHJhbmsodGFpbCwgZWxlbWVudHMgLSB7aGVhZH0pCgoKZGVmIF91cGRhdGVfcChwOiBsaXN0W2ludF0sIGk6IGludCkgLT4gTm9uZToKICAiIiJVcGRhdGUgYSBjYW5kaWRhdGUgdW5yYW5rZWQgcGVybXV0YXRpb24KCiAgQXJnczoKICAgIHA6IFRoZSBwZXJtdXRhdGlvbiB0byB1cGRhdGUKICAgIGk6IFRoZSBpbmRleCBvZiB0aGUgbGVmdG1vc3QgZWxlbWVudCB0byBhdHRlbXB0IHRvCiAgICAgIGluY3JlbWVudAoKICBSZXR1cm5zOgogICAgTm9uZQogICIiIgoKICBwW2ldICs9IDEKICBwcmV2ID0gcFs6aV0KICB3aGlsZSBwW2ldIGluIHByZXY6CiAgICBwW2ldICs9IDEKICBmb3IgaiBpbiByYW5nZShpICsgMSwgbGVuKHApKToKICAgIHBbal0gPSAwCiAgICBwcmV2ID0gcFs6al0KICAgIHdoaWxlIHBbal0gaW4gcHJldjoKICAgICAgcFtqXSArPSAxCgoKZGVmIHVucmFuayhpbmRleCwgaywgbik6CiAgIiIiVW5yYW5rIGEgcGVybXV0YXRpb24gb2YgayBvdXQgb2YgbiB0aGluZ3MuCgogIEFyZ3M6CiAgICBpbmRleDogVGhlIHJhbmsgb2YgdGhlIHBlcm11dGF0aW9uIHRvIHVucmFuawogICAgazogVGhlIGNhcmRpbmFsaXR5IG9mIHRoZSBwZXJtdXRhdGlvbgogICAgbjogVGhlIHNpemUgb2YgdGhlIHNldCB0aGUgcGVybXV0YXRpb24gaXMgY2hvc2VuCiAgICAgIGZyb20sIHdoaWNoIGlzIGVudW1lcmF0ZWQgY29uc2VjdXRpdmVseSBmcm9tIHplcm8KCiAgUmV0dXJuczoKICAgIFRoZSB1bnJhbmtlZCBwZXJtdXRhdGlvbgogICIiIgoKICBlbGVtZW50cyA9IHNvcnRlZChyYW5nZShuKSkKICBwID0gZWxlbWVudHNbOmtdCiAgZm9yIGkgaW4gcmFuZ2Uoayk6CiAgICBpZiBwW2ldID09IG4gLSAxOgogICAgICBjb250aW51ZQogICAgd2hpbGUgVHJ1ZToKICAgICAgciA9IHJhbmsocCwgc2V0KGVsZW1lbnRzKSkKICAgICAgaWYgciA+IGluZGV4OgogICAgICAgIGJyZWFrCiAgICAgIHByZXYgPSBwWzpdCiAgICAgIF91cGRhdGVfcChwLCBpKQogICAgcCA9IHByZXYKICByZXR1cm4gcAoKCiMgVGVzdCByYW5rIGFuZCB1bnJhbmsgYnkgY2hlY2tpbmcgcmV2ZXJzaWJpbGl0eQpudW1fdGVzdHMgPSAxCm1heF9uID0gODAwCm1heF9rID0gMjQKCgpmb3IgXyBpbiByYW5nZShudW1fdGVzdHMpOgogIG4gPSByYW5kb20ucmFuZGludCg1LCBtYXhfbikKICBrID0gcmFuZG9tLnJhbmRpbnQoMiwgbWluKG1heF9rLCBuKSkKCiAgcCA9IHJhbmRvbS5zYW1wbGUocmFuZ2UobiksIGspCiAgZWxlbWVudHMgPSBzZXQocmFuZ2UobikpCiAgCiAgcmFua19wID0gcmFuayhwLCBlbGVtZW50cykKICB1bnJhbmtlZCA9IHVucmFuayhyYW5rX3AsIGssIG4pCgogIGlmIHVucmFua2VkICE9IHA6CiAgICBwcmludCgocCwgbiwgcmFua19wLCB1bnJhbmtlZCwgcmFuayh1bnJhbmtlZCwgZWxlbWVudHMpKSkKICAgIGJyZWFrCgpwcmludCgnRG9uZS4nKQ==