/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.benchmarks.client.impl; import java.util.Iterator; import java.util.List; import java.util.ArrayList; import java.util.Arrays; /** * Iterates over all the possible permutations available in a list of {@link * Iterable Iterables}. * * <p> * The simplest way to iterate over the permutations of multiple iterators is in * a nested for loop. The PermutationIterator turns that for loop inside out * into a single iterator, which enables you to access each permutation in a * piecemeal fashion. * </p> */ public class PermutationIterator implements Iterator<PermutationIterator.Permutation> { /** * A single permutation of all the iterators. Contains the current value of * each iterator for the permutation. */ public static class Permutation { private List<Object> values; public Permutation(List<?> values) { this.values = new ArrayList<Object>(values); } public List<Object> getValues() { return values; } @Override public String toString() { return values.toString(); } } public static void main(String[] args) { List<Iterable<String>> iterables = new ArrayList<Iterable<String>>(3); iterables.add(Arrays.asList("a", "b", "c")); iterables.add(Arrays.asList("1", "2", "3")); iterables.add(Arrays.asList("alpha", "beta", "gamma", "delta")); System.out.println("Testing normal iteration."); for (Iterator<Permutation> it = new PermutationIterator(iterables); it.hasNext();) { Permutation p = it.next(); System.out.println(p); } System.out.println("\nTesting skipping iteration."); Iterator<String> skipIterator = Arrays.asList("alpha", "beta", "gamma", "delta").iterator(); boolean skipped = true; String skipValue = null; for (PermutationIterator it = new PermutationIterator(iterables); it.hasNext();) { Permutation p = it.next(); if (skipped) { if (skipIterator.hasNext()) { skipValue = skipIterator.next(); skipped = false; } } System.out.println(p); Object value = p.getValues().get(p.getValues().size() - 1); if (value.equals(skipValue)) { it.skipCurrentRange(); skipped = true; } } } /** * Is this the first run? */ private boolean firstRun = true; /** * The iterator from every range. */ private List<Iterator<?>> iterators; /** * Are more permutations available? */ private boolean maybeHaveMore = true; /** * The {@code Iterables} to permute. */ private List<? extends Iterable<?>> iterables; /** * Did we just skip a range? If so, the values List already contains the * values of the next permutation. */ private boolean rangeSkipped = false; /** * The current permutation of values. */ private List<Object> values; /** * Constructs a new PermutationIterator that provides the values for each * possible permutation of <code>iterables</code>. * * @param iterables non-null. Each {@link Iterable} must have at least one * element. iterables.size() must be > 1 * * TODO(tobyr) Consider if empty Iterables ever make sense in the context of * permutations. */ public PermutationIterator(List<? extends Iterable<?>> iterables) { this.iterables = iterables; iterators = new ArrayList<Iterator<?>>(); for (Iterable<?> iterable : iterables) { iterators.add(iterable.iterator()); } values = new ArrayList<Object>(); } /** * Returns a new <code>Permutation</code> containing the values of the next * permutation. * * @return a non-null <code>Permutation</code> */ public boolean hasNext() { if (!maybeHaveMore) { return false; } // Walk the iterators from bottom to top checking to see if any still have // any available values for (int currentIterator = iterators.size() - 1; currentIterator >= 0; --currentIterator) { Iterator<?> it = iterators.get(currentIterator); if (it.hasNext()) { return true; } } return false; } public Permutation next() { assert hasNext() : "No more available permutations in this iterator."; if (firstRun) { // Initialize all of our iterators and values on the first run for (Iterator<?> it : iterators) { values.add(it.next()); } firstRun = false; return new Permutation(values); } if (rangeSkipped) { rangeSkipped = false; return new Permutation(values); } // Walk through the iterators from bottom to top, finding the first one // which has a value available. Increment it, reset all of the subsequent // iterators, and then return the current permutation. for (int currentIteratorIndex = iterators.size() - 1; currentIteratorIndex >= 0; --currentIteratorIndex) { Iterator<?> it = iterators.get(currentIteratorIndex); if (it.hasNext()) { values.set(currentIteratorIndex, it.next()); for (int i = currentIteratorIndex + 1; i < iterators.size(); ++i) { Iterable<?> resetIterable = iterables.get(i); Iterator<?> resetIterator = resetIterable.iterator(); iterators.set(i, resetIterator); values.set(i, resetIterator.next()); } return new Permutation(values); } } throw new AssertionError( "Assertion failed - Couldn't find a non-empty iterator."); } public void remove() { throw new UnsupportedOperationException(); } /** * Skips the remaining set of values in the bottom {@code Iterable}. This * method affects the results of both hasNext() and next(). */ public void skipCurrentRange() { rangeSkipped = true; for (int currentIteratorIndex = iterators.size() - 2; currentIteratorIndex >= 0; --currentIteratorIndex) { Iterator<?> it = iterators.get(currentIteratorIndex); if (it.hasNext()) { values.set(currentIteratorIndex, it.next()); for (int i = currentIteratorIndex + 1; i < iterators.size(); ++i) { Iterable<?> resetIterable = iterables.get(i); Iterator<?> resetIterator = resetIterable.iterator(); iterators.set(i, resetIterator); values.set(i, resetIterator.next()); } return; } } maybeHaveMore = false; } }