1 /* Copyright (C) 2009 Gilleain Torrance <gilleain.torrance@gmail.com> 2 * 3 * Contact: cdk-devel@lists.sourceforge.net 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public License 7 * as published by the Free Software Foundation; either version 2.1 8 * of the License, or (at your option) any later version. 9 * All we ask is that proper credit is given for our work, which includes 10 * - but is not limited to - adding the above copyright notice to the beginning 11 * of your source code files, and to any copyright notice that you may distribute 12 * with programs based on this work. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public License 20 * along with this program; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 */ 23 package org.openscience.cdk.graph; 24 25 import java.util.Random; 26 27 28 /** 29 * General permutation generator, that uses orderly generation by ranking and 30 * unranking. The basic idea is that all permutations of length N can be ordered 31 * (lexicographically) like: 32 * <pre> 33 * 0 [0, 1, 2] 34 * 1 [0, 2, 1] 35 * 2 [1, 0, 2] 36 * ... 37 * </pre> 38 * where the number to the left of each permutation is the <i>rank</i> - really 39 * just the index in this ordered list. The list is created on demand, by a 40 * process called <i>unranking</i> where the rank is converted to the 41 * permutation that appears at that point in the list. 42 * 43 * <p>The algorithms used are from the book "Combinatorial Generation : 44 * Algorithms, Generation, and Search" (or C.A.G.E.S.) by D.L. Kreher and D.R. 45 * Stinson. CRC Press (18 Dec 1998). ISBN-13 : 978-0849339882.</p> 46 * 47 * @author maclean 48 * @cdk.created 2009-09-09 49 * @cdk.keyword permutation 50 * @cdk.module standard 51 * @cdk.githash 52 */ 53 public class Permutor { 54 55 /** 56 * The current rank of the permutation to use 57 */ 58 private int currentRank; 59 60 /** 61 * The maximum rank possible, given the size 62 */ 63 private int maxRank; 64 65 /** 66 * The number of objects to permute 67 */ 68 private int size; 69 70 /** 71 * For accessing part of the permutation space 72 */ 73 private Random random; 74 75 /** 76 * Create a permutor that will generate permutations of numbers up to 77 * <code>size</code>. 78 * 79 * @param size the size of the permutations to generate 80 */ Permutor(int size)81 public Permutor(int size) { 82 this.currentRank = 0; 83 this.size = size; 84 this.maxRank = this.calculateMaxRank(); 85 this.random = new Random(); 86 } 87 hasNext()88 public boolean hasNext() { 89 return this.currentRank < this.maxRank; 90 } 91 92 /** 93 * Set the permutation to use, given its rank. 94 * 95 * @param rank the order of the permutation in the list 96 */ setRank(int rank)97 public void setRank(int rank) { 98 this.currentRank = rank; 99 } 100 101 /** 102 * Set the currently used permutation. 103 * 104 * @param permutation the permutation to use, as an int array 105 */ setPermutation(int[] permutation)106 public void setPermutation(int[] permutation) { 107 this.currentRank = this.rankPermutationLexicographically(permutation); 108 } 109 110 /** 111 * Get the current rank. 112 * 113 * @return the current rank 114 */ getRank()115 public int getRank() { 116 return currentRank; 117 } 118 119 /** 120 * Randomly skip ahead in the list of permutations. 121 * 122 * @return a permutation in the range (current, N!) 123 */ getRandomNextPermutation()124 public int[] getRandomNextPermutation() { 125 int d = maxRank - currentRank; 126 int r = this.random.nextInt(d); 127 this.currentRank += Math.max(1, r); 128 return this.getCurrentPermutation(); 129 } 130 131 /** 132 * Get the next permutation in the list. 133 * 134 * @return the next permutation 135 */ getNextPermutation()136 public int[] getNextPermutation() { 137 this.currentRank++; 138 return this.getCurrentPermutation(); 139 } 140 141 /** 142 * Get the permutation that is currently being used. 143 * 144 * @return the permutation as an int array 145 */ getCurrentPermutation()146 public int[] getCurrentPermutation() { 147 return this.unrankPermutationLexicographically(currentRank, size); 148 } 149 150 /** 151 * Calculate the max possible rank for permutations of N numbers. 152 * 153 * @return the maximum number of permutations 154 */ calculateMaxRank()155 public int calculateMaxRank() { 156 return factorial(size) - 1; 157 } 158 159 // much much more efficient to pre-calculate this (or lazily calculate) 160 // and store in an array, at the cost of memory. factorial(int i)161 private int factorial(int i) { 162 if (i > 0) { 163 return i * factorial(i - 1); 164 } else { 165 return 1; 166 } 167 } 168 169 /** 170 * Convert a permutation (in the form of an int array) into a 'rank' - which 171 * is just a single number that is the order of the permutation in a lexico- 172 * graphically ordered list. 173 * 174 * @param permutation the permutation to use 175 * @return the rank as a number 176 */ rankPermutationLexicographically(int[] permutation)177 private int rankPermutationLexicographically(int[] permutation) { 178 int rank = 0; 179 int n = permutation.length; 180 int[] counter = new int[n + 1]; 181 for (int i = 1; i < permutation.length; i++) { 182 counter[i] = permutation[i - 1] + 1; 183 } 184 for (int j = 1; j <= n; j++) { 185 rank += (counter[j] - 1) * factorial(n - j); 186 for (int i = j + 1; i <= n; i++) { 187 if (counter[i] > counter[j]) { 188 counter[i]--; 189 } 190 } 191 } 192 return rank + 1; 193 } 194 195 /** 196 * Performs the opposite to the rank method, producing the permutation that 197 * has the order <code>rank</code> in the lexicographically ordered list. 198 * 199 * As an implementation note, the algorithm assumes that the permutation is 200 * in the form [1,...N] not the more usual [0,...N-1] for a list of size N. 201 * This is why there is the final step of 'shifting' the permutation. The 202 * shift also reduces the numbers by one to make them array indices. 203 * 204 * @param rank the order of the permutation to generate 205 * @param size the length/size of the permutation 206 * @return a permutation as an int array 207 */ unrankPermutationLexicographically(int rank, int size)208 private int[] unrankPermutationLexicographically(int rank, int size) { 209 int[] permutation = new int[size + 1]; 210 permutation[size] = 1; 211 for (int j = 1; j < size; j++) { 212 int d = (rank % factorial(j + 1)) / factorial(j); 213 rank = rank - d * factorial(j); 214 permutation[size - j] = d + 1; 215 for (int i = size - j + 1; i <= size; i++) { 216 if (permutation[i] > d) { 217 permutation[i]++; 218 } 219 } 220 } 221 222 // convert an array of numbers like [1...n] to [0...n-1] 223 int[] shiftedPermutation = new int[size]; 224 for (int i = 1; i < permutation.length; i++) { 225 shiftedPermutation[i - 1] = permutation[i] - 1; 226 } 227 return shiftedPermutation; 228 } 229 230 } 231