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 }