PermutationIterator.java

  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.  *      http://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. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.NoSuchElementException;
  26. import java.util.Objects;

  27. /**
  28.  * This iterator creates permutations of an input collection, using the
  29.  * Steinhaus-Johnson-Trotter algorithm (also called plain changes).
  30.  * <p>
  31.  * The iterator will return exactly n! permutations of the input collection.
  32.  * The {@code remove()} operation is not supported, and will throw an
  33.  * {@code UnsupportedOperationException}.
  34.  * </p>
  35.  * <p>
  36.  * NOTE: in case an empty collection is provided, the iterator will
  37.  * return exactly one empty list as result, as 0! = 1.
  38.  * </p>
  39.  *
  40.  * @param <E>  the type of the objects being permuted
  41.  * @since 4.0
  42.  */
  43. public class PermutationIterator<E> implements Iterator<List<E>> {

  44.     /**
  45.      * Permutation is done on these keys to handle equal objects.
  46.      */
  47.     private final int[] keys;

  48.     /**
  49.      * Mapping between keys and objects.
  50.      */
  51.     private final Map<Integer, E> objectMap;

  52.     /**
  53.      * Direction table used in the algorithm:
  54.      * <ul>
  55.      *   <li>false is left</li>
  56.      *   <li>true is right</li>
  57.      * </ul>
  58.      */
  59.     private final boolean[] direction;

  60.     /**
  61.      * Next permutation to return. When a permutation is requested
  62.      * this instance is provided and the next one is computed.
  63.      */
  64.     private List<E> nextPermutation;

  65.     /**
  66.      * Standard constructor for this class.
  67.      * @param collection  the collection to generate permutations for
  68.      * @throws NullPointerException if coll is null
  69.      */
  70.     public PermutationIterator(final Collection<? extends E> collection) {
  71.         Objects.requireNonNull(collection, "collection");
  72.         keys = new int[collection.size()];
  73.         direction = new boolean[collection.size()];
  74.         Arrays.fill(direction, false);
  75.         int value = 1;
  76.         objectMap = new HashMap<>();
  77.         for (final E e : collection) {
  78.             objectMap.put(Integer.valueOf(value), e);
  79.             keys[value - 1] = value;
  80.             value++;
  81.         }
  82.         nextPermutation = new ArrayList<>(collection);
  83.     }

  84.     /**
  85.      * Indicates if there are more permutation available.
  86.      * @return true if there are more permutations, otherwise false
  87.      */
  88.     @Override
  89.     public boolean hasNext() {
  90.         return nextPermutation != null;
  91.     }

  92.     /**
  93.      * Returns the next permutation of the input collection.
  94.      * @return a list of the permutator's elements representing a permutation
  95.      * @throws NoSuchElementException if there are no more permutations
  96.      */
  97.     @Override
  98.     public List<E> next() {
  99.         if (!hasNext()) {
  100.             throw new NoSuchElementException();
  101.         }

  102.         // find the largest mobile integer k
  103.         int indexOfLargestMobileInteger = -1;
  104.         int largestKey = -1;
  105.         for (int i = 0; i < keys.length; i++) {
  106.             if (direction[i] && i < keys.length - 1 && keys[i] > keys[i + 1] ||
  107.                 !direction[i] && i > 0 && keys[i] > keys[i - 1]) {
  108.                 if (keys[i] > largestKey) { // NOPMD
  109.                     largestKey = keys[i];
  110.                     indexOfLargestMobileInteger = i;
  111.                 }
  112.             }
  113.         }
  114.         if (largestKey == -1) {
  115.             final List<E> toReturn = nextPermutation;
  116.             nextPermutation = null;
  117.             return toReturn;
  118.         }

  119.         // swap k and the adjacent integer it is looking at
  120.         final int offset = direction[indexOfLargestMobileInteger] ? 1 : -1;
  121.         final int tmpKey = keys[indexOfLargestMobileInteger];
  122.         keys[indexOfLargestMobileInteger] = keys[indexOfLargestMobileInteger + offset];
  123.         keys[indexOfLargestMobileInteger + offset] = tmpKey;
  124.         final boolean tmpDirection = direction[indexOfLargestMobileInteger];
  125.         direction[indexOfLargestMobileInteger] = direction[indexOfLargestMobileInteger + offset];
  126.         direction[indexOfLargestMobileInteger + offset] = tmpDirection;

  127.         // reverse the direction of all integers larger than k and build the result
  128.         final List<E> nextP = new ArrayList<>();
  129.         for (int i = 0; i < keys.length; i++) {
  130.             if (keys[i] > largestKey) {
  131.                 direction[i] = !direction[i];
  132.             }
  133.             nextP.add(objectMap.get(Integer.valueOf(keys[i])));
  134.         }
  135.         final List<E> result = nextPermutation;
  136.         nextPermutation = nextP;
  137.         return result;
  138.     }

  139.     @Override
  140.     public void remove() {
  141.         throw new UnsupportedOperationException("remove() is not supported");
  142.     }

  143. }