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