1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4.iterators;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.NoSuchElementException;
27 import java.util.Objects;
28
29 /**
30 * This iterator creates permutations of an input collection, using the
31 * Steinhaus-Johnson-Trotter algorithm (also called plain changes).
32 * <p>
33 * The iterator will return exactly n! permutations of the input collection.
34 * The {@code remove()} operation is not supported, and will throw an
35 * {@code UnsupportedOperationException}.
36 * </p>
37 * <p>
38 * NOTE: in case an empty collection is provided, the iterator will
39 * return exactly one empty list as result, as 0! = 1.
40 * </p>
41 *
42 * @param <E> the type of the objects being permuted
43 * @since 4.0
44 */
45 public class PermutationIterator<E> implements Iterator<List<E>> {
46
47 /**
48 * Permutation is done on these keys to handle equal objects.
49 */
50 private final int[] keys;
51
52 /**
53 * Mapping between keys and objects.
54 */
55 private final Map<Integer, E> objectMap;
56
57 /**
58 * Direction table used in the algorithm:
59 * <ul>
60 * <li>false is left</li>
61 * <li>true is right</li>
62 * </ul>
63 */
64 private final boolean[] direction;
65
66 /**
67 * Next permutation to return. When a permutation is requested
68 * this instance is provided and the next one is computed.
69 */
70 private List<E> nextPermutation;
71
72 /**
73 * Standard constructor for this class.
74 *
75 * @param collection The collection to generate permutations for
76 * @throws NullPointerException if coll is null
77 */
78 public PermutationIterator(final Collection<? extends E> collection) {
79 Objects.requireNonNull(collection, "collection");
80 keys = new int[collection.size()];
81 direction = new boolean[collection.size()];
82 Arrays.fill(direction, false);
83 int value = 1;
84 objectMap = new HashMap<>();
85 for (final E e : collection) {
86 objectMap.put(Integer.valueOf(value), e);
87 keys[value - 1] = value;
88 value++;
89 }
90 nextPermutation = new ArrayList<>(collection);
91 }
92
93 /**
94 * Indicates if there are more permutation available.
95 *
96 * @return true if there are more permutations, otherwise false
97 */
98 @Override
99 public boolean hasNext() {
100 return nextPermutation != null;
101 }
102
103 /**
104 * Returns the next permutation of the input collection.
105 *
106 * @return A list of the permutator's elements representing a permutation
107 * @throws NoSuchElementException if there are no more permutations
108 */
109 @Override
110 public List<E> next() {
111 if (!hasNext()) {
112 throw new NoSuchElementException();
113 }
114
115 // find the largest mobile integer k
116 int indexOfLargestMobileInteger = -1;
117 int largestKey = -1;
118 for (int i = 0; i < keys.length; i++) {
119 if (direction[i] && i < keys.length - 1 && keys[i] > keys[i + 1] ||
120 !direction[i] && i > 0 && keys[i] > keys[i - 1]) {
121 if (keys[i] > largestKey) { // NOPMD
122 largestKey = keys[i];
123 indexOfLargestMobileInteger = i;
124 }
125 }
126 }
127 if (largestKey == -1) {
128 final List<E> toReturn = nextPermutation;
129 nextPermutation = null;
130 return toReturn;
131 }
132
133 // swap k and the adjacent integer it is looking at
134 final int offset = direction[indexOfLargestMobileInteger] ? 1 : -1;
135 final int tmpKey = keys[indexOfLargestMobileInteger];
136 keys[indexOfLargestMobileInteger] = keys[indexOfLargestMobileInteger + offset];
137 keys[indexOfLargestMobileInteger + offset] = tmpKey;
138 final boolean tmpDirection = direction[indexOfLargestMobileInteger];
139 direction[indexOfLargestMobileInteger] = direction[indexOfLargestMobileInteger + offset];
140 direction[indexOfLargestMobileInteger + offset] = tmpDirection;
141
142 // reverse the direction of all integers larger than k and build the result
143 final List<E> nextP = new ArrayList<>();
144 for (int i = 0; i < keys.length; i++) {
145 if (keys[i] > largestKey) {
146 direction[i] = !direction[i];
147 }
148 nextP.add(objectMap.get(Integer.valueOf(keys[i])));
149 }
150 final List<E> result = nextPermutation;
151 nextPermutation = nextP;
152 return result;
153 }
154
155 /**
156 * Always throws {@link UnsupportedOperationException}.
157 *
158 * @throws UnsupportedOperationException Always thrown.
159 */
160 @Override
161 public void remove() {
162 throw new UnsupportedOperationException("remove() is not supported");
163 }
164
165 }