How can I get randperm to return a permutation of a vector that has no entries at their original positions?
7 views (last 30 days)
I want take a random permutation of a vector such that all entries of the vector move to a new location.
For example, if I have a vector [1,2,3,4,5], then the following permutations are acceptable:
[2,1,4,5,3], [3,1,5,2,4], [5,4,2,3,1], etc.
However, for me, the following vector is not acceptable:
because the "3" has remained in the same location.
The "randperm" function in MATLAB allows for some of the entries in the vector to stay in the same position. Is there some way to use randperm that stops it from doing this? Or is there some other function out there that I am missing? (I have also looked at the functions "datasample" and "randsample" but they also do not seem to allow for this).
More Answers (4)
Bruno Luong on 28 Feb 2021
Edited: Bruno Luong on 28 Feb 2021
Here is an implementation of a non-rejection method and unbiased random derangement:
function p = randder(n)
% p = randder(n)
% Generate a random derangement if length n
% n: scalar integer >= 2
% p: array of size (1 x n), such that
% unique(p) is equal to (1:n)
% p(i) ~= i for all i = 1,2,....n.
% Base on: "Generating Random Derangement", Martinez Panholzer, Prodinger
% Remove the need inner loop by skrink index table J (still not ideal)
% See also: randperm
p = 1:n;
b = true(1,n);
m = n-1;
J = 1:m;
i = n;
u = n;
utab = 1:n;
qtab = (utab-1).*subfactorial(utab-2)./subfactorial(utab);
overflowed = ~isfinite(qtab);
qtab(overflowed) = 1./utab(overflowed);
x = rand(1,n);
r = rand(1,n);
k = ceil(x(i)*m);
j = J(k);
p([i j]) = p([j i]);
if r(i) < qtab(u)
b(j) = false;
J(k:m-1) = J(k+1:m);
m = m-1;
u = u-1;
u = u-1;
i = i-1;
m = m-1;
end % randder
function D = subfactorial(n)
D = floor((gamma(n+1)+1)/exp(1));
It might be slower but it's a non rejection method, so at least the run-time is always predictable.
7 6 1 3 9 5 10 4 2
Check validity and uniformity
m = 100000;
n = 6;
D=arrayfun(@(x) randder(n), 1:m, 'UniformOutput', false);
OK = all(U-(1:n),'all') && ...
~any(sort(U,2)-(1:n),'all'); % must be true
fprintf('All derangements are valid\n');
% Check uniformity
nbins = min(1000,size(U,1));
Jeff Miller on 26 Feb 2021
I don't think randperm can do that by itself, but I think this would work for an even number of items in the original vector:
orig = 1:10; % an example for the original vector of items
nvec = numel(orig);
halfn = nvec/2;
perm1 = randperm(nvec);
final = zeros(size(orig));
swap1pos = perm1(ivec);
swap2pos = perm1(ivec+halfn);
final(swap1pos) = orig(swap2pos);
final(swap2pos) = orig(swap1pos);
If you have an odd number of items in the original vector, handle one item specially and use this method with the remaining ones.
Would not be surprised if someone has a better solution, though.
Image Analyst on 27 Feb 2021
Just keep looping until there are no matches, like this:
n = 5;
originalVector = 1 : n;
maxIterations = 10000;
loopCounter = 1;
while loopCounter < maxIterations
newVector = randperm(n);
matches = newVector == originalVector;
break; % Break out of loop if there are no matches.
loopCounter = loopCounter + 1;
% Print out newVector to command window.
fprintf('Found answer after %d iterations.\n', loopCounter);
David Goodmanson on 28 Feb 2021
Edited: David Goodmanson on 28 Feb 2021
the methods I have seen here seem to involve trying randperm and rejecting the result if an element remains in the same location. Here is a method that uses the cycle structure of the permutation and does not allow any 1-cycles (element stays where it is). Randperm is called once. The code uses the fact that if you have n elements, and do a chain of randomly chosen elements starting with a given element, the odds that you obtain a k-cycle is 1/n for every k.
I don't know how the speed is compared to the rejection method, but this code is not slow, taking half a second for 10 million elements.
n = 100
q = randperm(n);
p = zeros(1,n);
nrem = n; % number of remaining elements
cycstruct = ; % cycle structure (just the lengths)
while nrem > 0
if nrem == 2;
cyc = 2;
cyc = randi(nrem-2)+1; % cycle length
if cyc == nrem-1; % unallowed cycle length
cyc = nrem;
cycstruct = [cycstruct cyc];
nrem = nrem-cyc;
ind = q(1:cyc);
q(1:cyc) = ;
p(ind) = circshift(ind,-1);
[1:n; p]; % p is the result
any(p==1:n) % check if any element stays at home
any(diff(sort(p))~=1) % any duplicated elements?