Permutations/Rank of a permutation

From Rosetta Code
Revision as of 22:46, 25 October 2012 by rosettacode>Paddy3118 (New draft task and Python solution.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Permutations/Rank of a permutation is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

A particular ranking of a permutation associates an integer with a particular ordering of all the permutations of a set of distinct items. For our purposes the ranking will assign integers to an ordering of all the permutations of the integers .

For example, the permutations of the digits zero to 3 arranged lexicographically have the following rank:

  PERMUTATION      RANK
  (0, 1, 2, 3) ->  0
  (0, 1, 3, 2) ->  1
  (0, 2, 1, 3) ->  2
  (0, 2, 3, 1) ->  3
  (0, 3, 1, 2) ->  4
  (0, 3, 2, 1) ->  5
  (1, 0, 2, 3) ->  6
  (1, 0, 3, 2) ->  7
  (1, 2, 0, 3) ->  8
  (1, 2, 3, 0) ->  9
  (1, 3, 0, 2) -> 10
  (1, 3, 2, 0) -> 11
  (2, 0, 1, 3) -> 12
  (2, 0, 3, 1) -> 13
  (2, 1, 0, 3) -> 14
  (2, 1, 3, 0) -> 15
  (2, 3, 0, 1) -> 16
  (2, 3, 1, 0) -> 17
  (3, 0, 1, 2) -> 18
  (3, 0, 2, 1) -> 19
  (3, 1, 0, 2) -> 20
  (3, 1, 2, 0) -> 21
  (3, 2, 0, 1) -> 22
  (3, 2, 1, 0) -> 23

Algorithms exist that can generate a rank from a permutation for some particular ordering of permutations, and that can generate the same rank from the given individual permutation (i.e. given a rank of 17 produce (2, 3, 1, 0) in the example above).

One use of such algorithms could be in generating a small, random, sample of permutations of items without duplicates when the total number of permutations is large. Remember that the total number of permutations of items is given by which grows large very quickly: A 32 bit integer can only hold , a 64 bit integer only . It becomes difficult to take the straight-forward approach of generating all permutations then taking a random sample of them.

A question on the Stack Overflow site asked how to generate one million random and indivudual permutations of 144 items.

This task is to
  1. Create a function to generate a permutation from a rank.
  2. Create the inverse function that given the permutation generates its rank.
  3. Show that for the two functions are indeed inverses of each other.
  4. Compute and show here 4 random, individual, samples of permutations of 13 objects.
Stretch goal
  • State how reasonable it would be to use your program to address the limits of the Stack Overflow question.
References
  1. Ranking and Unranking Permutations in Linear Time by Myrvomd & Ruskey. (Also available via Google here).
  2. Ranks on the DevData site.

Python

This is based on the work shown in the paper by Myrvomd & Ruskey and has algorithms for the two types of ordering of permutations that they show.
Their algorithm is efficient and pythons transparent use of arbitrary precision integers allows the Stack Overflow questions limits to be used. (Chopped at four rather than a million random samples although function get_random_ranks(144, 1e6) doesn't take long).

<lang python>from math import factorial as fact from random import randint from textwrap import wrap

def identity_perm(n):

   return list(range(n))

def unranker1(n, r, pi):

   if n:
       n1, (rdivn, rmodn) = n-1, divmod(r, n)
       pi[n1], pi[rmodn] = pi[rmodn], pi[n1]
       unranker1(n1, r // n, pi)
   return pi

def init_pi1(n, pi):

   pi1 = [-1] * n
   for i in range(n): 
       pi1[pi[i]] = i
   return pi1

def ranker1(n, pi, pi1):

   if n == 1: 
       return 0
   n1 = n-1
   s = pi[n1]
   pi[n1], pi[pi1[n1]] = pi[pi1[n1]], pi[n1]
   pi1[s], pi1[n1] = pi1[n1], pi1[s]
   return s + n * ranker1(n1, pi, pi1)

def unranker2(n, r, pi):

   if n > 0:
       n1 = n-1
       s = r // fact(n1)
       try:
           pp = pi[:]
           pi[n1], pi[s] = pi[s], pi[n1]
       except:
           print n, r, pp, n1, s
           raise
       unranker2(n1, r - s * fact(n1), pi)
   return pi

def ranker2(n, pi, pi1):

   if n == 1: 
       return 0
   n1 = n-1
   s = pi[n1]
   pi[n1], pi[pi1[n1]] = pi[pi1[n1]], pi[n1]
   pi1[s], pi1[n1] = pi1[n1], pi1[s]
   return s * fact(n1) + ranker2(n1, pi, pi1)

def get_random_ranks(permsize, samplesize):

   perms = fact(permsize)
   ranks = set(randint(0, perms) for r in range(samplesize))
   while len(ranks) < samplesize:
       ranks |= set( randint(0, perms) 
                     for r in range(samplesize - len(ranks)) )
   return ranks    

def test1(comment, unranker, ranker):

   n, samplesize, n2 = 3, 4, 13
   print(comment)
   perms = []
   for r in range(fact(n)):
       pi = identity_perm(n)
       perm = unranker(n, r, pi)
       perms.append((r, perm))
   for r, pi in perms:
       pi1 = init_pi1(n, pi)
       print('  From rank %2i to %r back to %2i' % (r, pi, ranker(n, pi[:], pi1)))
   print('\n  %i random individual samples of %i items:' % (samplesize, n2))
   for r in get_random_ranks(n2, samplesize):
       pi = identity_perm(n2)
       print('    ' + ' '.join('%2i' % i for i in unranker(n2, r, pi)))
   print()

def test2(comment, unranker):

   samplesize, n2 = 4, 144
   print(comment)
   print('  %i random individual samples of %i items:' % (samplesize, n2))
   for r in get_random_ranks(n2, samplesize):
       pi = identity_perm(n2)
       print('    ' + '\n      '.join(wrap(repr(unranker(n2, r, pi)))))
   print()

if __name__ == '__main__':

   test1('First ordering:', unranker1, ranker1)
   test1('Second ordering:', unranker2, ranker2)
   test2('First ordering, large number of perms:', unranker1)</lang>
Sample output
First ordering:
  From rank  0 to [1, 2, 0] back to  0
  From rank  1 to [2, 0, 1] back to  1
  From rank  2 to [1, 0, 2] back to  2
  From rank  3 to [2, 1, 0] back to  3
  From rank  4 to [0, 2, 1] back to  4
  From rank  5 to [0, 1, 2] back to  5

  4 random individual samples of 13 items:
     7  3  4  6 10  2  1 12  5  0  8 11  9
    12 10  2  0  7  8  1  9  3  6  4 11  5
     9  6 10 12  4 11  5  1  2  8  0  7  3
    12  0  7  5  8  1 11  3  6  4  2  9 10

Second ordering:
  From rank  0 to [1, 2, 0] back to  0
  From rank  1 to [2, 1, 0] back to  1
  From rank  2 to [2, 0, 1] back to  2
  From rank  3 to [0, 2, 1] back to  3
  From rank  4 to [1, 0, 2] back to  4
  From rank  5 to [0, 1, 2] back to  5

  4 random individual samples of 13 items:
     1 10  4  9 12  5  3  8 11  7  0  2  6
    12  6  5  3 10  4  8  2  1  0  7 11  9
     8 10  3  1  5  7 12 11  6  4  2  0  9
     3  8  7  9 12  0  1  5  2 10  6  4 11

First ordering, large number of perms:
  4 random individual samples of 144 items:
    [18, 38, 126, 65, 51, 55, 139, 97, 22, 135, 101, 1, 91, 141, 140, 59,
      16, 34, 90, 121, 137, 6, 136, 109, 123, 28, 41, 105, 0, 94, 78, 116,
      33, 82, 74, 68, 128, 14, 36, 117, 27, 32, 3, 143, 131, 47, 15, 77,
      119, 127, 49, 107, 45, 35, 96, 43, 84, 54, 104, 106, 60, 46, 95, 64,
      120, 11, 133, 53, 39, 56, 24, 25, 81, 20, 86, 72, 88, 17, 7, 73, 79,
      130, 37, 112, 134, 69, 138, 5, 19, 110, 102, 75, 31, 80, 103, 118, 29,
      40, 108, 42, 115, 48, 58, 70, 13, 26, 12, 125, 8, 9, 114, 98, 57, 71,
      129, 67, 52, 10, 66, 76, 93, 44, 23, 61, 2, 132, 87, 89, 21, 63, 85,
      92, 50, 142, 100, 111, 83, 124, 30, 62, 113, 4, 122, 99]
    [19, 91, 110, 21, 36, 43, 29, 137, 13, 135, 101, 93, 108, 42, 143,
      128, 58, 119, 68, 62, 139, 59, 77, 72, 121, 123, 28, 87, 6, 100, 0,
      84, 25, 14, 104, 86, 98, 69, 133, 74, 140, 102, 141, 48, 60, 103, 30,
      76, 5, 45, 3, 55, 8, 32, 112, 132, 2, 54, 41, 64, 11, 67, 20, 117,
      124, 52, 34, 142, 94, 85, 115, 47, 130, 66, 97, 92, 79, 70, 39, 15,
      61, 95, 106, 22, 136, 125, 23, 116, 18, 78, 109, 134, 51, 9, 83, 81,
      111, 120, 80, 96, 126, 107, 16, 105, 89, 38, 1, 56, 33, 40, 65, 88,
      44, 75, 122, 73, 27, 10, 7, 46, 99, 50, 17, 26, 12, 127, 131, 113, 57,
      114, 53, 71, 4, 138, 82, 24, 129, 90, 35, 31, 37, 49, 118, 63]
    [55, 137, 14, 34, 2, 1, 141, 110, 43, 133, 62, 28, 71, 63, 5, 143, 73,
      72, 78, 16, 125, 122, 68, 91, 11, 96, 94, 100, 69, 50, 66, 12, 67, 77,
      117, 61, 106, 83, 17, 107, 21, 105, 130, 65, 60, 56, 111, 7, 29, 19,
      82, 24, 26, 42, 44, 20, 30, 124, 136, 132, 57, 116, 127, 134, 142, 39,
      38, 95, 139, 6, 15, 75, 109, 87, 131, 104, 70, 45, 31, 140, 121, 80,
      22, 27, 49, 76, 40, 112, 102, 119, 97, 46, 84, 53, 0, 113, 36, 86, 37,
      123, 51, 52, 88, 48, 128, 118, 18, 93, 114, 74, 54, 81, 10, 98, 89,
      25, 58, 33, 129, 138, 3, 115, 103, 79, 135, 47, 59, 85, 64, 92, 126,
      41, 35, 8, 4, 108, 13, 23, 99, 9, 90, 120, 32, 101]
    [87, 133, 86, 85, 4, 93, 138, 95, 39, 136, 96, 1, 59, 110, 48, 142,
      99, 113, 116, 111, 94, 77, 105, 24, 100, 9, 141, 122, 97, 114, 130,
      135, 140, 139, 40, 88, 101, 7, 35, 98, 44, 20, 78, 132, 102, 2, 74,
      10, 30, 91, 5, 126, 90, 73, 49, 76, 67, 137, 134, 125, 38, 28, 92,
      121, 27, 70, 54, 31, 60, 72, 120, 83, 119, 118, 129, 128, 58, 123, 12,
      81, 0, 34, 33, 103, 19, 63, 124, 75, 117, 68, 13, 37, 79, 21, 32, 65,
      107, 82, 61, 109, 66, 26, 53, 18, 41, 50, 64, 106, 23, 11, 17, 62, 71,
      22, 43, 46, 89, 69, 115, 55, 36, 52, 104, 143, 47, 108, 14, 56, 51,
      127, 42, 15, 16, 45, 8, 80, 131, 57, 112, 84, 29, 25, 3, 6]