O'Connor - RPGLab: A Matlab Package For Random Permutation Generation
O'Connor - RPGLab: A Matlab Package For Random Permutation Generation
Derek O’Connor
1 Introduction
Permutations and combinations have been studied seriously for at least 500 years. Some
of the best mathematicians have contributed to this research: Bernoulli, Newton, Stir-
ling, Euler, . . ., etc. This area of study has expanded greatly in the last 100 years and is
now called Combinatorics, with permutations and combinations forming the foundation.
That is, you can’t do combinatorics unless you know (and mind) your P s and C s.
Permutations and combinations are considered to be so important that even school-
children are required to study the subject. Figure 1 shows the opening page of the
P&C chapter in Hall’s Algebra, which I bought as a secondary school pupil in 1960, price
10s/6d. Note that they use the good-old-fashioned notation nCr rather than the ambigu-
ous (nr). They also use nPr , but I don’t know the modern equivalent.1
This is an outline of these notes:
* Started: 3rd Jan 2010. Web: http://www.derekro
onnor.net email : derekro
onnoreir
om.net
1 There is no need for all this notational fuss: C ( n, k ) for combinations and P (n, k) for permutations will
do nicely.
1
Derek O’Connor Random Permutation Generation
Figure 1. H. S. H ALL , An Algebra for Schools, 1 ST E DITION 1912, REPRINTED 1956, M ACMILLAN , L ONDON
2 Definitions
In what follows we assume that A is the set {1, 2, . . . , n}. We will represent any per-
mutation as a M ATLAB vector p(1 : n), where p(i ) is the position of element i in the
permutation. Thus we have
p
{ a1 , a2 , . . . , a n } −
→ { a p ( 1) , a p ( 2) , . . . , a p ( n ) } (2.1)
i 1 2 3 4 5 6 7 8 9 10
p (i ) 3 4 9 2 10 8 6 1 5 7
n N = n!
101 107
102 10158
103 102,568
104 1035,659
105 10456,573
106 105,565,709
107 1065,657,059
108 10756,570,556
109 108,565,705,523
1010 1095,657,055,186
Definition 2. (Identity.) The identity permutation is p = [1, 2, . . . , n], that is, p(i ) = i, i =
1, 2, . . . , n. In M ATLAB the statement p = 1:n; generates the identity permutation.
Definition 4. (Fixed Point.) A fixed point of a permutation p is any i such that p(i ) = i. It
is called a fixed point because the element ai does not move under the permutation. The
Identity Permutation p = (1, 2, . . . , n) has n fixed points.
Definition 7. (Cyclic Permutation.) A cyclic permutation has 1 cycle. That is, starting at
any point i we have a sequence i → p(i ) → · · · → j → p( j) → i, which includes every
element of {1, 2, . . . , n}. This cycle has length n.
It should be obvious that a cyclic permutation has no fixed points and is, therefore, a
derangement. Equally obvious is that not all derangements are cyclic permutations.
3 Random Permutations
1 5 2 3 4 6 8 7 9 10
5 1 3 6 8 4 2 7 10 9
p 5 3 6 8 1 4 7 2 10 9
1 2 3 4 5 6 7 8 9 10
The Number of Fixed Points. The probability that a permutation of length n has k fixed
points is
1
Pr( p has k fixed points ) ∼ , (3.1)
ek!
and the average number of cycles of length k in a permutation of length n is 1/k with
variance 1/k. Hence, the average number of fixed points in a permutation of length n is 1, with
variance 1. This is a surprising result because the average is independent of the length of
the permutation.
The Number of Derangements. Let D (n) be the number of derangements in the set of
n! permutations of length n. Then
n
(−1)k n! + 1
n!
D (n) = n! ∑ = → , as n → ∞. (3.2)
k=0
k! e e
Hence
D (n) 1
→ ≈ 0.36788.
Pr( p is a derangement) = (3.3)
n! e
This means that in a large sample of random permutations about 37% of them will be
derangements, independent of n, the length of the permutation.
The Number of Cycles. The number of cycles in a random permutation ranges from 1,
a cyclic permutation, to n, the identity permutation. Let Ck (n) be the number of cycles of
length k in a permutation p(1 : n).
The expected number of cycles of length less than or equal to m is Hm .
The expected number of cycles of any length is Hn , or about log n. The average length of
n
a cycle is thus .
log n
Cn ( n ) 1
Cn (n) = (n − 1)! and Pr( p(n) is cyclic) = = . (3.4)
n! n
This means that in a large sample of random permutations a decreasing fraction of them
will be cyclic (e.g., 1% for n = 100, 0.1% for n = 1000), while the fraction of derangements
remains constant at 37%.
2 Most of what follows is from Sedgewick & Flajolet, Analysis of Algorithms, Addison-Wesley, 1996, Chap-
ter 6.
In the next section we will discuss the two main algorithms for generating random per-
mutations. However, before we do this we should have methods for checking the prop-
erties of random permutations. This will allow us to check the output of any generator
so that we do not waste time developing bad algorithms and programs that implement
them. These methods have been collected in a package called RPGL AB which is dis-
cussed in Section 8.
57
34 46
1
84
30
3
18
68
64
67
19
55 95 20
65
2
14 69
60 91
96
93
87
92 39
25 8 97 43 53
52 45
13 61
90 79 5
59
66 51
17 50
35
37 7 62
32 54
10 22
58
63
49
29 15
74
82
31
33
85 99
41 40
94
56
38 77
9
81
83 73
16
42 11 44
21 98
75 86
70
27 6 24
26
28
72 4 71
47
36
78 89
12
80
76 23
88 48
3 Figure 3 produced by the M ATLAB function GrVizp.m and GraphViz with ‘neato’ layout.
84 56
102 23
130 74
20 41
2 77
127 118
119 55
78
128
144
94
121
92
45
38
104
59
22
14
116
90
82
80
96
12
70
149
25 147
150 68 3
137
50 8
4 103
39
34
123 93
126 95 85
69 142
148 15
17
141 32
81 29
140 136 6
1
110 72
120 139
135
122 51
125 27 146
52 21 13
48
10 57
54 53
63 5
49
129
65 138
24
46
67 117
86
31
133 112
33
83 11
60 134
61 143
26 44 75
101
97 7
124
100 79 47
107 91
35
64 145 73
43
40
62 131
19
113 106 111
66
30 28
132
58 105
89
114
37
36
98
99
9
115 87
42 88
109 18
76 16
108 71
137 7 103
195
68
118
189
34
11
166
121
48
28
3
99
180
120
172
40
9
24
156
190
74 86
23 52 147
126
44 1
76
50 15
200
38 26
73
29
70 64
161
168
138
132 158
154 185
124
65
12 108 80 179
153
19 110 130
199 104 53 90
67 51 159
92
96
101 21 174
20 58
46 66 18 5
181 42
139 143
54
145
93
167 173
106
32
144
83 142 72
155 194
59
136 87
71 119
111 94 160
116 186
100 127
10 43 131 35 157
81 107
69
163
57
117
151
188 36
148
141 62
191 150
196 25 114
182
61
102
13
37 60 129
33
152
184
128 177
183 193
165
41 77
79
169 170
84
30
75 63
113
112
146 98
49
22 109
6
16 4
2
125
115
135
133 176
134 78 105
82
192
31
56
91
88
175 164
187
197
95 17
89 122
85
140 198
171
45 14
162
39
55 178
123 47 149
97 27
Algorithms and programs for generating random permutations have been around since
the start of the computer age. In fact Knuth has tracked down the first known algorithm
to R. A. Fisher and F. Yates, Statistical Tables, (London, 1938), Example 12.4 There appear
to be just two types of algorithm which are based on: (1) Random transpositions, and (2)
Sorting a random vector.
The production of random permutations may be viewed in different ways:
Algorithm RandPerm(S) →p
Notice that all the elements of S will be chosen and put into the array p. The array
is needed to preserve the random order in which the elements were drawn. Thus p is
returned containing a random permutation of S.
If we assume that each set operation can be performed in O(1) time, then this algorithm
runs in O(n) time, which is optimal up to a multiplicative constant. There are many ways
to implement the set operations, either explicitly or implicitly.
where π is a permutation of length n, and rand(i, j) returns a random integer from the
set {i, i + 1, . . . , j − 1, j}. This is a truly elegant algorithm: it is tiny, uses no extra space,
and is optimal. Also, it can shuffle any array in place, not just permutations.7
Hence any sorting algorithm will generate, implicitly or explicitly, a permutation and its
associated sorted vector:
i 1 2 3 4 5 6 7 8
x 0.146529 0.118308 0.315581 0.683211 0.914784 0.912839 0.753141 0.437558
s 0.118308 0.146529 0.315581 0.437558 0.683211 0.753141 0.912839 0.914784
p 2 1 3 5 8 7 6 4
x [ p] 0.118308 0.146529 0.315581 0.437558 0.683211 0.753141 0.912839 0.914784
We can see that the permutation p has one fixed point p[3] = 3, and three cycles (1, 2, 1),
(4, 5, 8, 4), and (6, 7, 6).
A random permutation is obtained from the sorting algorithm if it is given a randomly-
ordered vector to sort. This is done by filling a vector r[1 : n] with random numbers
5 RichardDurstenfeld, Algorithm 235, Random Permutation, Communications of the ACM, Vol. 7, July
1964, page 420.
6 Edward M. Reingold, Jurg Nievergelt, and Narsingh Deo, Combinatorial Algorithms: Theory and Practice,
~wagnerr/shue/. Elsewhere on his site is his C++ implementation of the Mersenne Twister random number
generator.
distributed uniformly on (0, 1) : r[1 : n] := RandReal(0, 1), and the sorting this random
vector : [s, p] := Sort(r[1 : n]), where s = r[ p]. Thus a random permutation operating on
a random vector gives an ordered vector.
Here are the two algorithms, side-by-side for easy comparison:
The M ATLAB functions here are all minor variations of the Fisher-Yates Shuffle algorithm,
except M ATLAB’s own randperm which uses sorting.
In the M ATLAB functions that follow, all loops are incremented rather than decremented.
In the original Knuth algorithm P, he decrements the loop because in those days this gave
faster loops. This is no longer true. 8
Fisher-Yates Implementations
function p = GRPfys(n)
p = 1:n; Identity permutation
for k = 2:n
r = ceil(k*rand); Random integer between 1 and k
t = p(r);
p(r) = p(k); Swap(p(r),p(k))
p(k) = t;
end;
return; GRPfys
Knuth, in the 3rd edition of Volume 2, TAOCP, points out in the second-last paragraph
on page 145 that there is no need for the swap if we don’t want to shuffle a given vector,
but just want a random permutation. This is implemented below as GRPNS
function p = GRPns(n)
p = 1:n; Identity permutation
for k = 2:n
r = ceil(k*rand); Random integer between 1 and k
p(k) = p(r); No Swap
p(r) = k;
end;
return; GRPns
The final variation of GRPfys is what all right-thinking M ATLAB-ers consider ‘good prac-
tice’, viz., vectorization. In this variation the generation of the random integer r is moved
8 The decremented (or backward) loop, and its close cousin, the zero-based array, have been constant
sources of error in the student programs that I have seen over the 25 years of teaching algorithms and data
structures. People from childhood have been taught to count from 1 upwards and mistakes are inevitable
when this is reversed. The rot set in, I believe, with Sputnik and the American Space Program – all those
Houston count-downs. And remember, Knuth started programming at that time.
Likewise with zero-based arrays: people do not naturally start counting from 0. Except of course in
Ireland and the UK. Here, in these sceptre’d isles, we count the floors of our buildings from 0 upwards. But
we don’t call the first floor Floor Zero. Oh no! We have a special name for it: the Ground Floor. I have
lost count (pardon the pun) of the number of Americans wandering lost around our university buildings
saying, “But Prof Jones states in his letter that his office is on the second floor of the Gerry Adams Memorial
Building”. M ATLAB , very sensibly, does not allow zero-based arrays, but I’m sure they get many requests
for them.
outside the loop and a random integer vector r is generated before entering the loop. Im-
mediately we see that this has doubled the memory required: two vectors p and r instead
of just p. But if it speeds up the function, it may be worth it.
function p = GRPvec(n);
p = I(n);
r = ceil(rand(n,1).*(1:n)’); Vector of random integers
for k = 2:n
t = p(r(k));
p(r(k)) = p(k); Swap(p(r),p(k))
p(k) = t;
end;
return; GRPvec
We can see in Table 5 that GRPVec takes 20% longer for n = 107 , and 6% longer for n = 108 ,
than the loop version. In this simple function vectorization has given us the worst of both
worlds: increased time and increased space. Vectorization, like the optimizing compiler,
seems to be a mixed blessing.
I decided to replace the 3-statement swap with a slick one-statement in the original Shuf-
fle method.
function p = GRPvswap(n)
p = 1:n;
for k = n:-1:2
r = ceil(k*rand);
1 t = p(r);
2 p([k r]) = p([r k]); p(r) = p(k);
3 p(k) = t;
end;
return; GRPvswap
This 1-statement swap had a disastrous effect on the execution time of this simple func-
tion. This function took 14 to 40 times longer to execute than the original. The slow-down
is caused by the need to construct two vectors [k r], [r k] for each swap. This is ex-
pensive, according to Jan Simon.9
9 http://www.mathworks.
om/matlab
entral/newsreader/view_thread/295414.
Jan Simon
function X = Shuffle(N,’index’)
% <--- snip --->
% INPUT:
% X: Array with any size. Types: DOUBLE, SINGLE, CHAR, LOGICAL,
% (U)INT64/32/16/8. This works for CELL and STRUCT arrays also, but
% shuffling their indices is faster.
% Author: Jan Simon, Heidelberg, (C) 2010
% Simple Knuth shuffle in forward direction:
for i = 1:length(X)
w = ceil(rand * i);
t = X(w);
X(w) = X(i);
X(i) = t;
end;
return; % Shuffle
This is Knuth’s Algorithm P with the loop direction reversed. Note that this function can
shuffle any vector, not just permutations. Also, it is very general and well-written, being
able to handle various integer arrays as well as the usual double precision array.
This M ATLAB function is not actually used. Instead, Simon has written this in C and put a
MEX wrapper around it so that when it is compiled it can be called from M ATLAB. This is
because the C compiler can generate much faster code than M ATLAB’s interpreter. Thus
this is a code generation improvement and not an algorithmic improvement.
M ATLAB
function p = randperm(n)
%RANDPERM Random permutation.
% RANDPERM(n) is a random permutation of the integers from 1 to n.
% For example, RANDPERM(6) might be [2 4 5 6 1 3].
%
% Note that RANDPERM calls RAND and therefore changes RAND’s state.
%
% See also PERMUTE.
% Copyright 1984-2004 The MathWorks, Inc.
% Revision : 5.10.4.1 Date : 2004/03/0221 : 48 : 27
[ignore,p] = sort(rand(1,n));
If you have a good sorting method then this is a quick ’n slick method for generating
random permutations. Others might call it quick ’n dirty. Yet others would call it just
dirty. But let’s not quibble: it works fine and it is fast because M ATLAB has carefully
implemented (in C or assembly?) a good sorting algorithm, Hoare’s Quicksort, I believe.
Although all the M ATLAB functions listed above use rand, which returns a standard IEEE
8-byte double precision floating point number, only M ATLAB uses a whole n−vector of
them. But the methods above generate n rands, don’t they? Yes, but one-at-a-time, so the
The results here show that all variants of GRPfys are about the same and so the choice
of function is reduced to three: GRPfys, GRPmex, or randperm. Jan Simon’s GRPmex is the
’mexed’ version of GRPfys, and runs 2–3 times faster. Both require 1 array of size n. If you
do not have the appropriate C compiler then you are forced to use GRPfys or something
similar.
M ATLAB’s randperm is the very slick one-liner [s,p] = sort(rand(n,1)). This method’s
complexity is O(n log n) which means that an O(n) method such as Shuffle will even-
tually beat it when n is large enough. If Tshuf = cshuf n and Tsort = csort n log n, then the
average times per array element are cshuf and csort log n. Thus the average time of M AT-
LAB’s sort method grows with n, while the Shuffle remains constant.
Table 5 shows that there is very little difference between GRPfys and randperm for n = 107
and n = 108 . But the crucial difference is that M ATLAB’s method uses twice as much
working memory as GRPfys. This is why there is a blank in the table for randperm at n =
109 : 16 GB memory was not enough to allow randperm to generate a random permutation
of size 8 bytes × 109 = 8 GB.
The general conclusion from these tests is that the simplest implementations of the Fisher-
Yates algorithm are the best. Fancy vectorizations and array index manipulations not
only obscure the algorithm, but are error-prone, and are slower than the simpler methods.
We can see that there are no ‘hot spots’ in this code and that time is fairly evenly spread
across each statement. This suggests that there is little we can do in M ATLAB to speed it
up. But what about vectorization?, I hear you cry. Let us try profiling GRPvec above. Here
are the results for n = 108 :
We can see that the unvectorized code GRPfys on the left requires 86.16/77.1 − 1 = 0.1175
= 12% more time that GRPvec. Random number generation takes 18.05 secs in GRPfys
but only 6.22 secs in GRPvec. The time taken by the other statements in the loop are about
the same for both versions. Hence the 12% difference is accounted for by the vectorization
of the random number generator. Or so it seems.
Because profiling adds a lot of overhead to the run times, it is always a good idea to get
the actual times without the profiler on. Still with the computer in the single cpu mode
GRPfys and GRPvec were timed, again with n = 108 . Here are the command-line results:
» tic;p=GRPvec(1e8);toc
Elapsed time is 27.283832 seconds.
» tic;p=GRPvec(1e8);toc
Elapsed time is 27.352696 seconds.
» tic;p=GRPfys(1e8);toc
Elapsed time is 23.726308 seconds.
» tic;p=GRPfys(1e8);toc
Elapsed time is 23.714992 seconds.
This shows that the GRPvec takes 27.3/23.7 − 1 = 15% more time than the unvectorized
GRPfys, a complete reversal of the profiler results. In this case the M ATLAB profiler has
been worse than useless — it has been grossly misleading. We can see that the profiler
added about (86.16 − 23.7)/23.7 = 263% overhead time to the actual running time of
GRPfys. This overhead time is noise (we don’t want it) which is swamping out the actual
time signal. The only reliable information given out by the profiler is the statement count.
But apart from the erroneous profiler results, this example shows that vectorization is
not (always) a good thing: not only has it added 15% to the run time, it has doubled the
working memory required.
“It was once true that vectorization would improve the speed of your MATLAB code.
However, that is largely no longer true with the JIT-accelerator” – Matlab Doug, Mar.
2010.
The Fisher-Yates Shuffle Algorithm is provably correct and, when implemented with a
good random number generator, produces all possible permutations with equal proba-
bility. A special permutation is one which has some special property or restriction: it is
cyclic, it is a derangement, it must have 2 fixed points, etc.
The simplest way to generate special permutations is by the Rejection Method, as shown
in the function GRPSpec: repeatedly generate a permutation until it has the special prop-
erty or restriction. The main problem with this general method is that many permutations
may need to be generated before a desirable one is found.
function GRPSpec(n) → p
Generates a special random permutation
of the integers 1,2,...,n
p := [1, 2, . . . , n] Identity perm.
while p is not special do
p := GRPfys(n)
endwhile p
return p
endfunc GRPSpec
This type of algorithm is called a Las Vegas Algorithm:10 an algorithm that is guaranteed to
give the correct output, but may take a very long (random) time to do so. It is important
to understand that GRPfys( p) is sampling uniformly with replacement from the set of all
permutations of length n, and knows nothing about the special property. We have seen
that the time complexity of GRPfys(n) is Ts (n) = cs n ∈ O(n). This is not a random time,
but constant for a given n.
The random uniform output of GRPfys( p) means that we do no know how many times
the while loop is performed: it is a random number nw . Hence the time complexity of
GRPSpec(n ) is a random function
nw
Tspec (n) = ∑ (Ttest (n) + Ts (n)). (6.1)
k=1
We have E [ Ts (n)] = Ts (n) is a constant for a given n, but we do not know much about
Ttest (n). Is it a random number or a constant? Usually these tests can be performed in
O(n) time, e.g., IsCyc( p) and IsDer( p) are O(n). Assuming that the test is , at worst, a
constant ct n, then we have E [ Tspec (n)] = E [nw ](ct n + cs n) = cnE [nw ].
What can be said about nw and its expected value? Obviously nw > 0 and nw < ∞,
assuming that the desired permutation type is not an impossibility, e.g., p(1 : 10) has at
least one p(i ) = 59, say. Consider asking for p = (n, n − 1, . . . , 2, 1), the reverse identity.
The probability of this permutation occurring is 1/n!, and so GRPfys may spend a very
long time until it ’hits’ it. An upper bound on nw would seem to be O(n!). This is, of
course, the essence of a Las Vegas algorithm: it will find the correct answer, but it may
take forever.
function p = GRDrej(n);
Generate a random permutation p(1:n) using GRPfys
and reject if this is not a derangement.
Requires expected e = 2.7183... passes through the while-loop.
NotDer = true;
while NotDer
p = GRPfys(n);
NotDer = HasFP(p); Derangement check
end;
return GRDrej
This is a new derangement algorithm by Martinez, et al.11 that is not easy to understand
but seems to work well. Shown below are the original algorithm and it M ATLAB imple-
mentation.
11 http://www.siam.org/pro
eedings/anal
o/2008/anl08_022martinez
.pdf
function GRPmpp(n) → p
Martinez et al’s original Algorithm which generates a random
derangement of the integers 1,2,...,n
p : = [1, 2, . . . , n ] Identity permutation
Mark[1 : n ] : =false;
i : = n; u : = n
while u ≥ 2 do
if ¬ Mark[i ] then
repeat
j : =Random(1, i − 1)
until ¬ Mark[ j]
p[i ] : = : p[ j ] Swap
u : =Uniform(0, 1)
if u < (u − 1) Du−2 /Du then
Mark[ j] : =true
u := u − 1
endif u
u := u − 1
endif ¬ Mark[i ]
i := i − 1
endwhile j
return p
endfunc GRPmpp
function p = GRDmpp(n);
p = I(n); identity permutation
mark = p < 0; mark(1:n) = false
i = n; u = n;
while u > 1
if ∼mark(i)
j = ceil(rand*(i-1)); random j in [1,i-1]
while mark(j)
j = ceil(rand*(i-1)); random j in [1,i-1]
end;
t = p(i);
p(i) = p(j); Swap p(i) and p(j)
p(j) = t;
r = rand;
if r < 1/u Prob. if test for n large
mark(j) = true;
u = u-1;
end;
u = u-1;
end; if ∼mark
i = i-1;
end; while u > 1
return GRDmpp
Tests on GRDmpp show that on average the outer while loop is performed 2 times, as
predicted by the analysis. Hence this is faster than the Rejection algorithm which requires
e = 2.718 loops, on average.
Timing tests were run on the two derangement functions along with GRDmex which is
GRDrej using Jan Simon’s fast GRPmex function.
The Rejection algorithm (GRDrej) and the Martinez algorithm (GRDmpp) are examples of
Las Vegas Algorithms, i.e., they are guaranteed to give the correct result but they may
run for a long (random) time. The Fisher-Yates Shuffle algorithm has a constant running
time for a given n, which I will call Ts (n). The running time of the Rejection algorithm
is Tr (n) = nw × ( Tt (n) + Ts (n)), where nw is a random variable that counts the number
of times the while loop is executed. The expected value of nw is e = 2.7183 . . . because
the probability of the Shuffle algorithm generating a derangement is 1/e. Hence Tr (n) =
e( Tt (n) + Ts (n)) = 2.7183Ts (n), if we assume that Tt (n) is negligible compared to Ts (n).
Martinez, et al., prove that the expected running time of their algorithm is Tm (n) = 2Ts (n)
and so it is faster, on average, than the Rejection algorithm, but not by much. My advice
would be: if you have a good fast Fisher-Yates Shuffle program, use it in the Rejection
algorithm, especially if you are risk-averse, because the Martinez algorithm is harder to
implement correctly. We can see in the timing table above that the Rejection algorithm
using Jan Simon’s GRPmex was fastest for n = 105 , n = 106 and n = 108 . GRDmpp was
fastest for n = 107 . An important added advantage of the Rejection algorithm is that it
uses just 1 array of length n, whereas the Martinez algorithm uses 2 arrays of length n.
The interesting question remains: is random derangement generation inherently more
difficult than random permutation generation, or is a there a derangement algorithm
with Td (n) = Ts (n) ?
A cyclic permutation has a single cycle of length n. A permutation can be tested in O(n)
time to see if it is cyclic. Hence GRPSpec will perform an O(n) cyclic test followed by an
O(n) permutation generation for each iteration of the while loop. If nw is the number
of times the while loop is performed then the complexity of this method is O(nw n).
From (3.4) we have Pr( p is cyclic) = 1/n. Hence the expected value of nw is n and so the
expected complexity of GRPSpec is O(n2 ), for cyclic permutations.
S. Sattola, in her Master’s thesis, gave a very simple modification of the Fisher-Yates
algorithm that generates random cyclic permutations.12
function GRCsat(n) → p
Generates a random cyclic permutation
of the integers 1,2,...,n
p := [1, 2, . . . , n] Identity perm.
for k := 2 to n do
r := RandInt(1, k − 1) Sattola’s modification
p [ k ] : = : p [r ] Swap
endfor k
return p
endfunc GRCsat
We can see that the only change in the Fisher-Yates algorithm is this: r := RandInt(1, k)
becomes r := RandInt(1, k − 1). This change ensures that it swaps different elements of
p, i.e, p[k] :=: p[r], k 6= r. It is remarkable that such a small change in the Fisher-Yates
algorithm can make such a big difference in its output.
The correctness of this algorithm has been proved and it has been thoroughly analysed
by Prodinger13 It obviously has the same O(n) complexity as the Fisher-Yates algorithm,
and so it is an order of magnitude faster than the rejection method.
12 S.
Sattola, “An algorithm to generate a random cyclic permutation”. Information Processing Letters, Vol.
22, pages 315–317, 1986.
13 Prodinger, Helmut, “On the analysis of an algorithm to generate a random cyclic permutation”, Ars
Combinatorial generation algorithms and programs are often small, deceptively simple,
subtle, and, above all, error-prone. These small programs are often buried deep as sub-
programs (functions) in large simulations whose operation depends crucially on their
correct and efficient working. It should go without saying that these generators must be
rigourously tested.
These tests are for any n, but usually with n in the range [106 , 1010 ], where exhaustive
testing is out of the question.
1. Existential. Does the generator output the correct form of combinatorial object. For
example, (1) does a permutation generator produce (all possible) permutations?
(2) does a random derangement generator produce (all possible) derangements?,
and (3) does a random cyclic permutation generator produce (all possible) cyclic
permutations.
2. Uniformity. Are the random permutations distributed uniformly over the popula-
tion of n! permutations. Uniformity implies that each number in {1, 2, . . . , n} occurs
with relative frequency 1/n.
4. Others
— TO BE COMPLETED —
target = [5 4 6 7 1 8 10 2 3 9];
[p,k,t] = FindPerm(target);
-------------------------------------------------------
Target NOT FOUND after k = 18144000 iterations. Time = 202.6715 secs.
Fraction of Space searched (k/n!) = 5 Rate = 89525 per second
ans =
5 4 6 7 1 8 10 2 3 9
6 5 4 1 10 8 3 2 7 9
Figure 6 confirms that the expected value and variance of the number of fixed points is 1,
for any n, and that the relative frequency of 0 fixed points is about 1/e ≈ 0.3679, which
is the relative frequency of derangements. A bad RPG would be unlikely to give these
results. Indeed, it was this test that showed that there was something wrong with GRDrej.
The fault was identified in the ‘simple’ IsDer interrogation function: a compound while
test was wrong. The function was re-written as IsDer(p) = ∼HasFP(p).
— TO BE COMPLETED —
8 RPGLab
In this section14 we describe a set of simple tools, M ATLAB functions, that help us ma-
nipulate and test permutations. This is done in the spirit of Kernighan and Plauger’s
Software Tools in Pascal, Addison-Wesley, 1981, one of the great books in computer sci-
ence. Sadly, the lessons and insights of this book are either unknown to or ignored by
many of today’s M ATLAB programmers. In the same spirit as Kernighan & Plauger, the
books by Jon Bentley are a devoted to small, simple, but powerful programs.
I hope that these simple functions will prove useful to those who wish to experiment
with random permutations. Hence the suffix L AB.
Naming Conventions. Mathematics derives its power from the judicious choice of sym-
bols it uses to name objects and processes. For example, ∑ni=1 xi , conveys an immense
amount of information in a very compact form. Expressing this in a programming lan-
guage (except APL) would take many more symbols, while expressing it in ordinary
English would take a paragraph or more. With this in mind we have used the short-
est possible names that are compatible with conveying information that is essential to
understanding a piece of code. The names we use are not explanations but symbolic
reminders of what the name stands for. Thus p = GRPfys(n) reminds us that it stands
for the process of Generating a Random Permutation of length n using the f isher-yates
shuffle algorithm, and stores it in the row vector p. This name and others have to be
studied, understood, and remembered, before they can be used with facility – just as in
Mathematics.
Error Checking. What may shock many M ATLABers is the lack of input error checking.
This is in the spirit of Kernighan & Plauger who assume that the users are reasonably
competent and not lazy. After all, the makers of 36" pipe wrenches don’t check to see if
you are using one to fix your bicycle.
If you generate and manipulate permutations with these functions only, then only valid
permutations will be generated. Otherwise you must do your own error-checking.
Comments. Another shock is that very few comments are used, except for a succinct
statement of purpose at the head of each function. It has become fashionable in some
quarters to write mini-theses at the start of each function, along with enough biographical
data to satisfy the Library of Congress. I do include my name in the header of each
function.15 The header comments in the functions that follow have been stripped out to
save space, but they are in the m-files.
14 I
have written this section to be independent of the others. As a result, some code and discussions are
repeated.
15 This reminds me of a comment by a famous art historian: “No matter how abstract the painting, the
’M ATLAB-isms’. Perhaps the biggest shock to M ATLABers will be that there is virtually
no use of M ATLAB’s vectorizations and fancy array manipulation functions. Most code is
written in loop or component form, as the Numerical Linear Algebra people call it. I be-
lieve that M ATLAB’s matrix-vector notation and manipulation functions are very useful,
when used in the proper place, but many M ATLABers have made a fetish of vectorization
and index manipulation, to the detriment of code clarity, and, quite often, to speed.
A benefit of not using what I call ‘M ATLAB-isms’, is that these functions are easily trans-
lated into other languages, such as Fortran and C, making them good Mex candidates.
M ATLAB is a very powerful and useful system for numerical computing. It allows the
knowledgeable user to do in a few lines what would take hundreds of lines of tedious
Fortran or C code. A superb example of the power of good M ATLAB programming is
Trefethen’s Chebfun system.16. Trefethen, by the way, claims to be the first license holder
of M ATLAB.
These are the low-level functions that are used everywhere and are so simple that they
are, we hope, obviously correct. Writing such simple functions is not a trivial task.
These primitives fall into two classes: (1) permutation constructors, and (2) permutation
interrogators.
8.2 Constructors
Identity. p = I(n)
function p = I(n);
p = 1:n; a row vector
return; I(n)
Transpose. p = Trans(p,i,j)
function p = Trans(p,i,j);
t = p(i);
p(i) = p(j);
p(j) = t;
return; Trans
Reverse. p = Rev(p,i,j)
function p = Rev(p,i,j);
while i < j
p = Trans(p,i,j);
i = i+1;
j = j-1;
end;
return; Rev
This code reverses elements p(i ) . . . p( j) of p(1, 2, . . . , n). 17 Although there is no error
i j
p 5 3 6 8 1 4 7 2 10 9
1 2 3 4 5 6 7 8 9 10
i j
p 5 3 7 8 1 4 6 2 10 9
1 2 3 4 5 6 7 8 9 10
i=j
p 5 3 7 4 1 8 6 2 10 9
1 2 3 4 5 6 7 8 9 10
checking, this code is quite robust: if i ≥ j then nothing happens. In practice, the trans-
position function is replaced by the 3-statement swap operation. This operation is now
used in the rotation operation in a very clever way.
17 See Kernighan & Plauger, Software Tools in Pascal, Addison-Wesley, 1981, pages 194,195.
Rotate. p = Rot(p,k)
function p = Rot(p,k);
n = length(p);
p = Rev(p,1,k);
p = Rev(p,k+1,n);
p = Rev(p,1,n);
return; Rot
This does a left circular shift of all elements of p by k positions. That is,
Rot( p,k)
[ p(1), . . . p(k), p(k + 1), . . . , p(n)] −−−−→ [ p(k + 1), . . . , p(n), p(1), . . . , p(k)]
Here is how the three reversals work:
Rev( p,1,k)
[ p(1), . . . p(k), p(k + 1), . . . , p(n)] −−−−−→ [ p(k), . . . , p(1), p(k + 1), . . . , p(n)]
Rev( p,k+1,n )
[ p(k), . . . p(1), p(k + 1), . . . , p(n)] −−−−−−−→ [ p(k), . . . , p(1), p(n), . . . , p(k + 1)]
Rev( p,1,n )
[ p(k), . . . p(1), p(n), . . . , p(k + 1)] −−−−−→ [ p(k + 1), . . . , p(n), p(1), . . . , p(k)]
At first glance, this code may seem inefficient because it is doing three reversals. Firstly,
it should be obvious that the Reverse function is efficient: it performs ( j − i )/2 transpo-
sitions, or a total of 3( j − i )/2 element moves. Hence, the Rotate operation performs
3( k − 1) 3( n − k − 1) 3( n − 1) 6n − 9
+ + = ∼ 3n element moves.
2 2 2 2
Although this is not optimal (each element is moved twice) it is very efficient nonetheless.
There is an extensive body of research literature on this and related topics. The rotation
operation is an important low-level operation in text editors.
function p = GRPfys(n);
p = I(n);
for k = 2:n
r = RandInt(1,k);
p = Trans(p,k,r);
end;
return; GRPfys
This is the increasing-loop version of the Durstenfeld version of the Fisher-Yates Shuffle
algorithm, shown below, along with Pike’s modification for a partial shuffle.
For those who have never seen, let alone written an Algol program, the function entier( x)
is the largest integer not greater than the value of x.18
18 Looking at Durstenfeld’s nicely-typeset Shuffle procedure, it is a shock to realize that it is a valid Algol
procedure, comments and all. Now, fifty years later, it is a poor reflection on today’s language designers,
compiler-interpreter writers, and program-editor makers, that they cannot handle or present us with nicely
typeset programs, despite the huge strides made in mathematical typesetting, a much more difficult task
than program typesetting. But we do have 19 different types of assignment statements in Java. Programmers
of the World, Protest!
function p = GRPmex(n);
p = ShuffleMex(n,’index’); Jan Simon’s mexed version of F-Y Shuffle
return; GRPmex
8.3 Interrogators
ans = true;
for k = 1:n
if count(p(k)) == 0
count(p(k)) = count(p(k))+1;
else
ans = false; Stop after first bad p(k)
return
end;
end; for k
return; IsPer
A permutation p is valid if and only if each k ∈ {1, 2, . . . , n} appears once and only once.
This is an expensive check because it uses an extra array.19 Notice that the function re-
turns as soon as a bad value is found. No further time is wasted checking for other bad
points. We will use this stop-as-soon-as-possible principle throughout the other interroga-
tion functions.
for k = 1:n
if p(k) == k
ans = true;
return; stops after first fixed point.
end;
end; for k
return HasFP
If you are sure that HasFP(p) is correct, then IsDer(p) is obviously correct. Replace the
function call with inline code if necessary, but check it carefully.20
19 I’m
sure there are better ways of doing this check. Later, maybe.
20 My
first attempt at IsDer(p) had an error in a compound while check. This error did not show up until
much later, when I did frequency tests on the permutation generators.
ans = L == n;
return; IsCyc
There are many special permutations. Here we give two which seem to be the most
useful: Derangement and Cyclic.
function p = GRDrej(n);
NotDer = true;
while NotDer
p = GRPfys(n);
NotDer = HasFP(p); Derangement check
end;
return GRDrej
function p = GRDmex(n);
NotDer = true;
while NotDer
p = GRPmex(n);
NotDer = HasFP(p); Derangement check
end;
return GRDmex
This is just GRDrej using GRPmex which uses Jan Simon’s ShuffleMex. This gives a 2 to 3
speedup.
function p = GRCsat(n)
p = I(n);
for k = 2:n
r = RandInt(1,k-1); Changing k to k-1 in GRPfys
p = Trans(p,k,r);
end;
return; GRCsat
This is Sattola’s modification of GRPfys. We can see that the only change in GRCsat is
to use of k − 1 instead of k. This causes a dramatic change in the output of this func-
tion: it generates cyclic permutations only. The correctness of this algorithm has been
proved and it has been thoroughly analysed by Prodinger21 It obviously has the same
O(n) complexity as the Fisher-Yates algorithm.
21 Prodinger, Helmut, “On the analysis of an algorithm to generate a random cyclic permutation”, Ars
We wish to prove that this statement works correctly, given that rand is M ATLAB’s imple-
mentation of the Mersenne Twister generator. M ATLAB’s documentation on rand states
that
The rand function now supports a method of random number generation called the
Mersenne Twister. The algorithm used by this method, developed by Nishimura and
Matsumoto, generates double precision values in the closed interval [2−53, 1 − 2−53 ],
with a period of (219937 − 1)/2
The period of rand is (219937 − 1)/2 ≈ 20 × 106000 , which is a gigantic number, at least
to ordinary mortals. However, the sets of permutations that GRPfys(n) samples from are
gigantically larger that rand’s gigantic period (See Table 2 above). On a 2.3 GHz, 16 GB
machine, random permutations of length n = 109 can be generated in a few minutes.
Thus we are sampling from a space of N = n! = (109 )! objects. Using Stirling’s approx-
imation we find loge (109 )! ≈ 2 × 1010 which means that N = n! is an integer with about
1010 digits. This means that despite rand’s gigantic period – an integer with a mere 6000
digits – GRPfys(n) will never visit more than an infinitesimally small fraction of the per-
mutation space. Yet we can say, with a certain amount of confidence, that GRPfys will
produce a permutation p(1 : 109 ) with probability 1/(1010 - digit number).
— TO BE COMPLETED —