Bag Permutation Generator

I'm afraid my command of Maple does not stretch to anything not easily portable to other languages; in addition, it relies on ancient features which I understand are nowadays deprecated.

A recent attempt at improving this situation - by providing type declarations for variables and parameters - proved so mind-bogglingly counter-productive that I was obliged immediately to reverse it.

As has already been observed above, there are a great many permutation generators around; though somehow or other, they never seem to do quite what one requires.

My own attempt appears below: it requires a sorted bag / multiset of integers. My excuse for posting it here is that a simple generator which employs adjacent transpositions where possible, in constant amortised time, does not appear to be readily available elsewhere. I have attempted to provide a (sparse) summary of the algorithm.

Any suggestions for updating the style would be welcome: in particular, a more object-oriented recasting might avoid having to cart around the auxiliary arrays as parameters (shades of FORTRAN!).

WFL

Added Oct 27th 2008: only just discovered that 3 occurrences of "m" in the test had mysteriously transmogrified into "n" --- corrected.

   1 ################################################################################
   2 
   3 # Generate bag-permutations of m elements, ordered by nearly adjacent
   4 # transposition; generator value = m+1 on termination;
   5 # assumes m > 0; 4 sentinels; no pre-initialisation.
   6 # Maple program, Fred Lunnon, Maynooth 30/10/08
   7 # Version with type-checking time 7x slower than without under Maple 9
   8 # --- commented out again!
   9 # A 'run' is defined to be a sequence of equally ranked elements in
  10 # consecutive locations (and with consecutive identifiers), such that all
  11 # but the righthand end (highest location) are drifting right (d = +1).
  12 # The drift of the righthand element at j determines that of the entire run.
  13 
  14 # A run is 'blocked' when the element adjacent in its direction of drift at i
  15 # has equal or lower rank. The element adjacent to a run at its lefthand end
  16 # is of different rank; at its righthand end may be another of equal rank.
  17 # Generator employs Steinhaus-Johnson-Trotter adjacent transposition strategy
  18 # to exchange the earliest (by identifier) unblocked run at j with the single
  19 # element at i. Individual elements within a run behave as though under the
  20 # action of a combination generator by nearly-adjacent transpositions;
  21 # all permutations of the user bag of ranks are transpositions, most of
  22 # which are actually adjacent.
  23 # Once an unblocked element is found, the attached run is shifted one place
  24 # along direction d, the end elements transposed, and all but the rightmost
  25 # drift set leftwards; the drift of all earlier elements is reversed.
  26 # In order to avoid unnecesary and nested loops, reversal takes place during
  27 # search, and the left end of a run is retained in l for future reference.
  28 # Spatial blocking sentinels ranked lowest are installed at either end, and
  29 # temporal termination sentinels ranked highest placed off the righthand end.
  30 
  31 # Case m = 0 is excluded, since iterator signals immediate termination;
  32 # could be fixed, at the cost of extra testing at its start.
  33 # The generator is stable, in the sense that elements of the same rank remain
  34 # always in the same order. For this reason it might be regarded as a
  35 # permutation generator with restrictions on the ordering of specified subsets.
  36 # Next move takes place in constant amortised time; always by transposition,
  37 # which will be adjacent when possible (for large m, at least 82.7% of moves).
  38 
  39 # Each element has unique identifier p, lower p drifting more frequently;
  40 # R_[k] = rank (positive integer user symbol) of element at k;
  41 # P_[k] = identifier p of element at k, 0 <= p < m+3;
  42 # Q_[p] = location k of element p, 0 <= k < m+3;
  43 # D_[k] = current drift direction d of element at k, -1 <= d <= +1.
  44 # i,j,k,l locate elements in current permutation;
  45 # d,e = direction of element at j,i; l holds left end of current run.
  46 # Arrays are extended by sentinels indexed 0 at left, m+1,m+2,m+3 at right;
  47 # list [...] subscripts run from 1 to op(list).
  48 
  49 # Initialiser: Input list of m > 0 rank values (non-decreasing order);
  50 # adjacency improves for increasing partition;
  51 # output R_, P_, Q_, D_ 1-dim arrays of integer.
  52 #initbagperm := proc (rank :: list(integer), R_, P_, Q_, D_) :: NULL;
  53 # local m :: integer, j :: integer, r :: array(integer),
  54 # r_1 :: integer, r_m :: integer;
  55 initbagperm := proc (rank, R_, P_, Q_, D_)
  56 local m, j, r, r_1, r_m;
  57 m := nops(rank); r := sort(rank);
  58 if m <= 0 then print(`bagperm: length <= 0`)
  59 else r_1, r_m := r[1], r[m];
  60 R_ := array(0..m+3, [r_1-1, seq(r[j], j = 1..m), r_1-1, r_m+1, r_m+2]);
  61 P_ := array(0..m+3, [0, seq(j, j = 1..m), 0, m+1, m+2]); # permed identities
  62 Q_ := array(0..m+3, [0, seq(j, j = 1..m), m+2, m+3, 0]); # inverse locations
  63 D_ := array(0..m+3, [0, seq(+1, j = 1..m), 0, +1, +1]); # permed drifts
  64 fi; NULL end;
  65 
  66 # Iterator: Output permuted ranks in R_[1..m];
  67 # result = location j of run drifted; on termination j = m+1.
  68 #nextbagperm := proc (R_ :: array(integer), P_ :: array(integer),
  69 # Q_ :: array(integer), D_ :: array(integer)) :: integer;
  70 # local i :: integer, j :: integer, k :: integer, l :: integer,
  71 # d :: integer, e :: integer, p :: integer;
  72 nextbagperm := proc (R_, P_, Q_, D_)
  73 local i, j, k, l, d, e, p;
  74 # locate earliest unblocked element at j, starting at blocked element 0
  75 j, i, d := 0, 0, 0;
  76 while R_[j] >= R_[i] do
  77 D_[j] := -d; # blocked at j; reverse drift d pre-emptively
  78 j := Q_[P_[j]+1]; d := D_[j]; i := j+d; # next element at j, neighbour at i
  79 if R_[j-1] <> R_[j] then l := j # save left end of run in l
  80 elif d < 0 then i := l-1 fi od; # restore left end at head of run
  81 # shift run of equal rank from i-d,i-2d,...,l to i,i-d,...,l+d
  82 k := i; if d < 0 then l := j fi;
  83 e := D_[i]; p := P_[i]; # save neighbour drift e and identifier p
  84 while k <> l do
  85 P_[k] := P_[k-d]; Q_[P_[k]] := k;
  86 D_[k] := -1; k := k-d od; # reset drifts of run tail elements
  87 R_[l], R_[i] := R_[i], R_[l]; # transpose user ranks
  88 D_[l], D_[i] := e, d; # restore drifts of head and neighbour
  89 P_[l], Q_[p] := p, l; # wrap neighbour around to other end
  90 j end;
  91 
  92 # Test bag-perms --- listing 
  93 parts := [2, 2, 2]; # chosen partition of set 
  94 n := nops(parts): m := add(parts[i], i = 1..n): # length and weight 
  95 symb := [seq(seq(i, j = 1..parts[i]), i = 1..n)]: 
  96 cardi := combinat[multinomial](m, op(parts)): # number of bag-perms 
  97 initbagperm(symb, R_,P_,Q_,D_): 
  98 n := 1: print(n, [seq(R_[i], i = 1..m)]); 
  99 while nextbagperm(R_,P_,Q_,D_) <= m and n < cardi + 2 do 
 100   n := n+1; print(n, [seq(R_[i], i = 1..m)]) od: 
 101 print(n, cardi); # equal 
 102 
 103 ################################################################################

Posted by FredLunnon on Mapleprimes.


See also MapleGrumbles by FredLunnon.


CategoryMaple

Maple/PermutationGenerator (last edited 2008-10-30 04:32:44 by FredLunnon)