1 /*
2 * LetterOrder - Experimenting with the order of letters in words
3 * Copyright (C) 2011-2015 Christian Schenk
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 package org.christianschenk.letterorder.utils;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23
24 /**
25 * Helps working with permutations.
26 *
27 * TODO: Implement caching of permutations.
28 *
29 * @author Christian Schenk
30 */
31 public class PermutationUtils {
32
33 /**
34 * Returns a permutation of n elements in random order.
35 *
36 * @param n
37 * number of elements
38 * @return random permutation
39 */
40 @SuppressWarnings("boxing")
41 public int[] getRandomPermutation(final int n) {
42 final List<Integer> ints = new ArrayList<Integer>(n);
43 for (int i = 0; i < n; i++)
44 ints.add(i);
45 Collections.shuffle(ints, RandomUtils.RANDOM);
46
47 // XXX: All that may be implemented in a better way...
48 final int[] realInts = new int[n];
49 for (int i = 0; i < n; i++)
50 realInts[i] = ints.get(i);
51
52 return realInts;
53 }
54
55 /**
56 * Implements the
57 * <a href="https://en.wikipedia.org/w/index.php?oldid=662333702">
58 * Steinhaus–Johnson–Trotter</a> algorithm.
59 *
60 * Uses primitive types due to performance considerations. The internal
61 * methods used here silently agree that e.g. the <code>work</code> and
62 * <code>dir</code> arrays are the same size thus minimizing any sanity
63 * checks in the code.
64 *
65 * Since the factorial of n yields large numbers even for small values
66 * of n one should make sure not to pass arbitrarily large numbers for n
67 * to this method: at best it simply causes an {@link OutOfMemoryError}
68 * or may run for a long time.
69 *
70 * @param n
71 * number of elements
72 * @return permutations
73 */
74 public int[][] getPermutations(final int n) {
75 return this.getPermutations(n, 0);
76 }
77
78 /**
79 * See {@link #getPermutations(int)}.
80 *
81 * @param n
82 * number of elements
83 * @param offset
84 * elements are initilized with this offset
85 * @return permutations
86 */
87 public int[][] getPermutations(final int n, final int offset) {
88 // Holds the results: n! * n elements
89 final int[][] results = new int[(int) this.factorial(n)][];
90 // 1, 2, ..., n
91 final int[] work = new int[n];
92 // 0 = left, 1 = right
93 final int[] dir = new int[n];
94
95 // Initialize
96 for (int i = 0; i < n; i++) {
97 work[i] = i + offset;
98 dir[i] = 0;
99 }
100
101 int step = 1;
102 results[0] = this.copy(work);
103 // print(work, dir);
104
105 while (this.hasMobile(work, dir)) {
106 final int curMobile = this.findLargestMobile(work, dir);
107
108 // swap, b = (a += b -= a) - b;
109 final int movePos = curMobile + (dir[curMobile] == 0 ? -1 : 1);
110 work[movePos] = (work[curMobile] += work[movePos] -= work[curMobile]) - work[movePos];
111 dir[movePos] = (dir[curMobile] += dir[movePos] -= dir[curMobile]) - dir[movePos];
112
113 // reverse direction
114 for (int i = 0; i < n; i++)
115 if (work[i] > work[movePos]) dir[i] = dir[i] == 0 ? 1 : 0;
116
117 // store
118 results[step] = this.copy(work);
119 step++;
120
121 // print(work, dir);
122 }
123
124 return results;
125 }
126
127 /**
128 * Returns true if a mobile integer exists otherwise false.
129 *
130 * @param work
131 * array of the integer values
132 * @param dir
133 * direction of the integer values
134 * @return true if mobile integer exists otherwise false
135 */
136 private boolean hasMobile(final int[] work, final int[] dir) {
137 for (int i = 0, n = work.length; i < n; i++) {
138 if (this.isMobile(work, dir, i)) return true;
139 }
140 return false;
141 }
142
143 /**
144 * Returns true if the given integer is mobile otherwise false. An
145 * integer is said to be mobile if, in the direction of its mobility,
146 * the nearest integer is less than the current integer. Note that if an
147 * integer is to the far left and its mobility is to the left, it is not
148 * mobile. Similarly, if an integer is to the far right and its mobility
149 * is to the right, it is also not mobile.
150 *
151 * @param work
152 * array of the integer values
153 * @param dir
154 * direction of the integer values
155 * @param i
156 * selected integer
157 * @return true if selected integer is mobile otherwise false
158 */
159 private boolean isMobile(final int[] work, final int[] dir, final int i) {
160 // leftmost integer pointing to the left is not mobile
161 // rightmost integer pointing to the right is not mobile
162 if ((i == 0 && dir[i] == 0) || (i == work.length - 1 && dir[i] == 1)) {
163 return false;
164 }
165 // An integer is mobile if, in the direction of its mobility, the
166 // nearest integer is less than the current integer.
167 if (i > 0 && dir[i] == 0 && work[i] > work[i - 1]) {
168 return true;
169 }
170 if (i < work.length - 1 && dir[i] == 1 && work[i] > work[i + 1]) {
171 return true;
172 }
173 if (i > 0 && i < work.length) {
174 if ((dir[i] == 0 && work[i] > work[i - 1]) || (dir[i] == 1 && work[i] > work[i + 1])) {
175 return true;
176 }
177 }
178
179 return false;
180 }
181
182 /**
183 * Returns the index of the largest mobile integer.
184 *
185 * @param work
186 * array of the integer values
187 * @param dir
188 * direction of the integer values
189 * @return index of largest mobile integer
190 */
191 private int findLargestMobile(final int[] work, final int[] dir) {
192 int largest = -1;
193 int pos = -1;
194 for (int i = 0, n = work.length; i < n; i++) {
195 if (this.isMobile(work, dir, i) && largest < work[i]) {
196 largest = work[i];
197 pos = i;
198 }
199 }
200 return pos;
201 }
202
203 /**
204 * Evaluates n!
205 *
206 * @param n
207 * number
208 * @return n!
209 */
210 private long factorial(final int n) {
211 if (n <= 1) return 1;
212 return n * this.factorial(n - 1);
213 }
214
215 /**
216 * Copies the given array and returns the copy.
217 *
218 * @param src
219 * source array
220 * @return array with copied data from source array
221 */
222 private int[] copy(final int[] src) {
223 final int[] dest = new int[src.length];
224 System.arraycopy(src, 0, dest, 0, src.length);
225 return dest;
226 }
227
228 /**
229 * Displays the integers of the work array and the direction of the
230 * individual items.
231 *
232 * @param work
233 * array of the integer values
234 * @param dir
235 * direction of the integer values
236 */
237 @SuppressWarnings("unused")
238 private void print(final int[] work, final int[] dir) {
239 for (int i = 0, n = work.length; i < n; i++)
240 System.out.print((dir[i] == 0 ? " <" : " ") + work[i] + (dir[i] == 1 ? ">" : " "));
241 System.out.println("");
242 }
243 }