View Javadoc
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 }