comparison scripts/specfun/perms.m @ 31100:05729b0e39e4

perms.m: Much faster technique to generate unique permutations
author Michael Leitner <michael.leitner@frm2.tum.de>
date Mon, 20 Jun 2022 17:27:39 -0400
parents b949f8a631e2
children 46e15523ca06
comparison
equal deleted inserted replaced
31099:7e6e70dd1c0d 31100:05729b0e39e4
32 ## @code{factorial (@var{n}) * @var{n}}, where @var{n} is the length of 32 ## @code{factorial (@var{n}) * @var{n}}, where @var{n} is the length of
33 ## @var{v}. Any repeated elements are included in the output. 33 ## @var{v}. Any repeated elements are included in the output.
34 ## 34 ##
35 ## If the optional argument @qcode{"unique"} is given, then only unique 35 ## If the optional argument @qcode{"unique"} is given, then only unique
36 ## permutations are returned, using less memory and generally taking less time 36 ## permutations are returned, using less memory and generally taking less time
37 ## than calling @code{unique (perms (@var{v}), "rows")}. In this case, the 37 ## than calling @code{unique (perms (@var{v}), "rows")}.
38 ## results are not guaranteed to be in any particular order.
39 ## 38 ##
40 ## Example 1 39 ## Example 1
41 ## 40 ##
42 ## @example 41 ## @example
43 ## @group 42 ## @group
81 80
82 if (nargin < 1) 81 if (nargin < 1)
83 print_usage (); 82 print_usage ();
84 endif 83 endif
85 84
85 unique_v = 0;
86 if (nargin == 2) 86 if (nargin == 2)
87 if (! strcmpi (opt, "unique")) 87 if (! strcmpi (opt, "unique"))
88 error ('perms: option must be the string "unique".'); 88 error ('perms: option must be the string "unique".');
89 else
90 unique_v = 1;
89 endif 91 endif
90 uniqv = unique (v); 92 endif
91 n = numel (uniqv); 93
92 if (n == 1)
93 P = v;
94 return;
95 elseif (n < numel (v))
96 P = unique_perms (v, uniqv);
97 return;
98 endif
99 endif
100
101 ## If we are here, then we want all permutations, not just unique ones.
102 ## Or the user passed the "unique" option but the input had no repetitions.
103 v = v(:).'; # convert to row vector 94 v = v(:).'; # convert to row vector
104 if (isnumeric (v) || ischar (v)) 95 if (isnumeric (v) || ischar (v))
105 ## Order of output is only dependent on the actual values for 96 ## Order of output is only dependent on the actual values for
106 ## character and numeric arrays. 97 ## character and numeric arrays.
107 v = sort (v, "ascend"); 98 v = sort (v, "ascend");
117 case 2 108 case 2
118 P = [v([2 1]);v]; 109 P = [v([2 1]);v];
119 case 3 110 case 3
120 P = v([3 2 1; 3 1 2; 2 3 1; 2 1 3; 1 3 2; 1 2 3]); 111 P = v([3 2 1; 3 1 2; 2 3 1; 2 1 3; 1 3 2; 1 2 3]);
121 endswitch 112 endswitch
113 if (unique_v)
114 P = unique (P, "rows");
115 endif
122 else 116 else
123 v = v(end:-1:1); 117 if (! unique_v)
124 n-= 1; 118 v = v(end:-1:1);
125 119 n-= 1;
126 idx = zeros (factorial (n), n); 120
127 idx(1:6, n-2:n) = [1, 2, 3;1, 3, 2;2, 1, 3;2, 3, 1;3, 1, 2;3, 2, 1]+(n-3); 121 idx = zeros (factorial (n), n);
128 f = 2; # jump-start for efficiency with medium n 122 idx(1:6, n-2:n) = [1, 2, 3;1, 3, 2;2, 1, 3;2, 3, 1;3, 1, 2;3, 2, 1]+(n-3);
129 for j = 3:n-1 123 f = 2; # jump-start for efficiency with medium n
130 b = 1:n; 124 for j = 3:n-1
131 f *= j; 125 b = 1:n;
132 perm = idx(1:f, n-(j-1):n); 126 f *= j;
133 idx(1:(j+1)*f, n-j) = (n-j:n)(ones (f, 1),:)(:); 127 perm = idx(1:f, n-(j-1):n);
134 for i=0:j 128 idx(1:(j+1)*f, n-j) = (n-j:n)(ones (f, 1),:)(:);
135 b(i+n-j) -= 1; 129 for i=0:j
136 idx((1:f)+i*f, n-(j-1):n) = b(perm); 130 b(i+n-j) -= 1;
131 idx((1:f)+i*f, n-(j-1):n) = b(perm);
132 endfor
137 endfor 133 endfor
138 endfor 134
139 135 n += 1;
140 n += 1; 136 f *= n-1;
141 f *= n-1; 137 P = v(1)(ones (factorial (n), n));
142 P = v(1)(ones (factorial (n), n)); 138 P(:,1) = v(ones (f, 1),:)(:);
143 P(:,1) = v(ones (f, 1),:)(:); 139
144 140 for i = 1:n
145 for i = 1:n 141 b = v([1:i-1 i+1:n]);
146 b = v([1:i-1 i+1:n]); 142 P((1:f)+(i-1)*f, 2:end) = b(idx);
147 P((1:f)+(i-1)*f, 2:end) = b(idx); 143 endfor
148 endfor 144 else # user wants unique permutations
145 [v, i, j] = unique(v);
146 h = accumarray (j, 1)';
147 idx = m_perms (h);
148 P = v (sortrows (idx'));
149 endif
149 endif 150 endif
150 151
151 endfunction 152 endfunction
152 153
153 function P = unique_perms (v, uniqv) 154 function out_perms = m_perms (multis)
154 lenvec = 1:numel (v); 155
155 n_uniq = numel (uniqv); 156 l = numel (multis);
156 157 if (l == 1)
157 ## Calculate frequencies. Slightly faster to not use hist() or histc(). 158 out_perms = uint8 (ones (multis, 1));
158 for i = n_uniq:-1:1 159 else
159 freq(i) = nnz (v == uniqv(i)); 160 p1 = m_perms (multis (1:floor (l/2)));
160 endfor 161 p2 = m_perms (multis (floor(l/2)+1:l)) + max (p1 (:, 1));
161 162 l1 = rows (p1);
162 ## Performance is improved by handling the most frequent elements first. 163 l2 = rows (p2);
163 ## This can change the output order though. Currently we do not promise 164 cp1 = columns (p1);
164 ## the results will be in any specific order. 165 cp2 = columns (p2);
165 [freq, order] = sort (freq, "descend"); 166
166 uniqv = uniqv(order); 167 p = nchoosek (1:l1+l2, l1);
167 168 rp = rows(p);
168 ## Call nchoosek for each choice and do a Cartesian set product, 169
169 ## avoiding collisions. 170 ii = false (l1+l2, rp);
170 idx = nchoosek (lenvec, freq(1)); 171 ii(p + (0:rp - 1)' * (l1 + l2)) = true;
171 for i = 2:n_uniq 172 out_perms = zeros (l1 + l2, cp1 * cp2 * rp, "uint8");
172 this = nchoosek (lenvec, freq(i)); 173 out_perms (repmat ( ii, cp1 * cp2, 1)(:)) = repmat (p1, cp2, rp)(:);
173 174 out_perms (repmat (!ii, cp1 * cp2, 1)(:)) = repmat (p2, 1, cp1 * rp)(:);
174 new = zeros (rows (idx) + 1e5, columns (idx) + columns (this)); 175 endif
175 ## Preallocate 1e5 extra rows.
176 pos = 0;
177 for j = 1:rows (this)
178 tmp = idx;
179 for k = this(j,:)
180 tmp = tmp(all (tmp != k, 2), :);
181 endfor
182
183 r = rows (tmp);
184 c = columns (tmp);
185 if (pos + r > rows (new)) # Allocate more memory on the fly
186 new(pos + r + 1e5, 1) = 0;
187 endif
188
189 new(pos+1:pos+r, 1:c) = tmp;
190 new(pos+1:pos+r, c+1:end) = repmat (this(j,:), r, 1);
191 pos += r;
192 endfor
193 idx = new(1:pos, :);
194 endfor
195 clear new tmp this # Free memory before building P
196
197 idx = (idx-1) * rows (idx) + (1:rows (idx))';
198
199 ## Initialize P to be the same size as idx with the same class as v.
200 P = repmat (v, rows(idx), 1);
201 pos = 0;
202 for i = 1:n_uniq
203 P(idx(:, (pos + 1):(pos + freq(i)))) = uniqv(i);
204 pos += freq(i);
205 endfor
206 176
207 endfunction 177 endfunction
208
209 178
210 %!assert (rows (perms (1:6)), factorial (6)) 179 %!assert (rows (perms (1:6)), factorial (6))
211 %!assert (perms (pi), pi) 180 %!assert (perms (pi), pi)
212 %!assert (perms ([pi, e]), [pi, e; e, pi]) 181 %!assert (perms ([pi, e]), [pi, e; e, pi])
213 %!assert (perms ([1,2,3]), [3,2,1;3,1,2;2,3,1;2,1,3;1,3,2;1,2,3]) 182 %!assert (perms ([1,2,3]), [3,2,1;3,1,2;2,3,1;2,1,3;1,3,2;1,2,3])