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 }