From e57282bd9cdde1c4f707dbe111459b4f8e370bde Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Wed, 27 May 2015 21:18:19 +0000 Subject: [PATCH] Add collating, zipping, reversing methods to FluentIterable, add ZippingIterator. git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1682121 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/collections4/FluentIterable.java | 97 +++++++++++- .../commons/collections4/IterableUtils.java | 124 ++++++++++++++- .../iterators/ZippingIterator.java | 141 ++++++++++++++++++ 3 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java diff --git a/src/main/java/org/apache/commons/collections4/FluentIterable.java b/src/main/java/org/apache/commons/collections4/FluentIterable.java index 91e604592..c4a4f9e04 100644 --- a/src/main/java/org/apache/commons/collections4/FluentIterable.java +++ b/src/main/java/org/apache/commons/collections4/FluentIterable.java @@ -18,6 +18,7 @@ package org.apache.commons.collections4; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; import java.util.List; @@ -151,6 +152,55 @@ public class FluentIterable implements Iterable { return of(IterableUtils.chainedIterable(iterable, other)); } + /** + * Returns a new FluentIterable whose iterator will traverse the + * elements of the current and provided iterable in natural order. + *

+ * Example: natural ordering + *

    + *
  • this contains elements [1, 3, 5, 7] + *
  • other contains elements [2, 4, 6, 8] + *
+ *

+ * The returned iterable will traverse the elements in the following + * order: [1, 2, 3, 4, 5, 6, 7, 8] + *

+ * A null iterable will be treated as an empty iterable. + * + * @param other the other iterable to collate, may be null + * @return a new iterable, collating this iterable with the other in natural order + * @see {@link org.apache.commons.collections4.iterators.CollatingIterator CollatingIterator} + */ + public FluentIterable collate(final Iterable other) { + return of(IterableUtils.collatedIterable(iterable, other, null)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse the + * elements of the current and provided iterable according to the + * ordering defined by an comparator. + *

+ * Example: descending order + *

    + *
  • this contains elements [7, 5, 3, 1] + *
  • other contains elements [8, 6, 4, 2] + *
+ *

+ * The returned iterable will traverse the elements in the following + * order: [8, 7, 6, 5, 4, 3, 2, 1] + *

+ * A null iterable will be treated as an empty iterable. + * + * @param other the other iterable to collate, may be null + * @param comparator the comparator to define an ordering, may be null, + * in which case natural ordering will be used + * @return a new iterable, collating this iterable with the other in natural order + * @see {@link org.apache.commons.collections4.iterators.CollatingIterator CollatingIterator} + */ + public FluentIterable collate(final Iterable other, Comparator comparator) { + return of(IterableUtils.collatedIterable(iterable, other, comparator)); + } + /** * This method fully traverses an iterator of this iterable and returns * a new iterable with the same contents, but without any reference @@ -202,6 +252,16 @@ public class FluentIterable implements Iterable { return of(IterableUtils.loopingIterable(iterable)); } + /** + * Returns a new FluentIterable whose iterator will traverse the + * elements from this iterable in reverse order. + * + * @return a new iterable, providing a reversed view of this iterable + */ + public FluentIterable reverse() { + return of(IterableUtils.reversedIterable(iterable)); + } + /** * Returns a new FluentIterable whose iterator will skip the first * N elements from this iterable. @@ -236,7 +296,36 @@ public class FluentIterable implements Iterable { public FluentIterable unique() { return of(IterableUtils.uniqueIterable(iterable)); } - + + /** + * Returns a new FluentIterable whose iterator will traverse + * the elements of this iterable and the provided elements in + * alternating order. + * + * @param elements the elements to interleave + * @return a new iterable, interleaving this iterable with the elements + */ + @SuppressWarnings("unchecked") + public FluentIterable zip(final E... elements) { + return zip(Arrays.asList(elements)); + } + + /** + * Returns a new FluentIterable whose iterator will traverse + * the elements of this iterable and the other iterable in + * alternating order. + * + * @param other the other iterable to interleave + * @return a new iterable, interleaving this iterable with others + */ + public FluentIterable zip(final Iterable... others) { + @SuppressWarnings("unchecked") + Iterable[] iterables = new Iterable[1 + others.length]; + iterables[0] = iterable; + System.arraycopy(others, 0, iterables, 1, others.length); + return of(IterableUtils.zippingIterable(iterables)); + } + // convenience methods // ---------------------------------------------------------------------- @@ -357,10 +446,10 @@ public class FluentIterable implements Iterable { } /** - * Returns a list containing all elements of this iterable by traversing - * its iterator. + * Returns a list containing all elements of this iterable by + * traversing its iterator. *

- * The returned list is mutable. + * The returned list is guaranteed to be mutable. * * @return a list of the iterable contents */ diff --git a/src/main/java/org/apache/commons/collections4/IterableUtils.java b/src/main/java/org/apache/commons/collections4/IterableUtils.java index 6bccbd64c..8e4ee1032 100644 --- a/src/main/java/org/apache/commons/collections4/IterableUtils.java +++ b/src/main/java/org/apache/commons/collections4/IterableUtils.java @@ -17,11 +17,14 @@ package org.apache.commons.collections4; import java.util.Collection; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.apache.commons.collections4.iterators.LazyIteratorChain; +import org.apache.commons.collections4.iterators.ReverseListIterator; import org.apache.commons.collections4.iterators.UniqueFilterIterator; +import org.apache.commons.collections4.iterators.ZippingIterator; /** * Provides utility methods and decorators for {@link Iterable} instances. @@ -136,6 +139,37 @@ public class IterableUtils { }; } + // Collated + // ---------------------------------------------------------------------- + + /** + * Combines the two provided iterables into an ordered iterable using the + * provided comparator. If the comparator is null, natural ordering will be + * used. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable, may be null + * @param b the second iterable, may be null + * @param comparator the comparator defining an ordering over the elements, + * may be null, in which case natural ordering will be used + * @return a filtered view on the specified iterable + */ + public static Iterable collatedIterable(final Iterable a, + final Iterable b, + final Comparator comparator) { + return new FluentIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.collatedIterator(comparator, + emptyIteratorIfNull(a), + emptyIteratorIfNull(b)); + } + }; + } + // Filtered // ---------------------------------------------------------------------- @@ -151,7 +185,8 @@ public class IterableUtils { * @return a filtered view on the specified iterable * @throws NullPointerException if predicate is null */ - public static Iterable filteredIterable(final Iterable iterable, final Predicate predicate) { + public static Iterable filteredIterable(final Iterable iterable, + final Predicate predicate) { if (predicate == null) { throw new NullPointerException("predicate must not be null."); } @@ -232,6 +267,38 @@ public class IterableUtils { }; } + // Reversed + // ---------------------------------------------------------------------- + + /** + * Returns a reversed view of the given iterable. + *

+ * In case the provided iterable is a {@link List} instance, a + * {@link ReverseListIterator} will be used to reverse the traversal + * order, otherwise an intermediate {@link List} needs to be + * created. + *

+ * The returned iterable's iterator supports {@code remove()} if the + * provided iterable is a {@link List} instance. + * + * @param the element type + * @param iterable the iterable to use, may be null + * @return a reversed view of the specified iterable + * @see ReverseListIterator + */ + public static Iterable reversedIterable(final Iterable iterable) { + return new FluentIterable() { + @Override + public Iterator iterator() { + final List list = (iterable instanceof List) ? + (List) iterable : + IteratorUtils.toList(emptyIteratorIfNull(iterable)); + + return new ReverseListIterator(list); + } + }; + } + // Skipping // ---------------------------------------------------------------------- @@ -276,7 +343,8 @@ public class IterableUtils { * @return a transformed view of the specified iterable * @throws NullPointerException if transformer is null */ - public static Iterable transformedIterable(final Iterable iterable, final Transformer transformer) { + public static Iterable transformedIterable(final Iterable iterable, + final Transformer transformer) { if (transformer == null) { throw new NullPointerException("transformer must not be null."); } @@ -310,6 +378,58 @@ public class IterableUtils { }; } + // Zipping + // ---------------------------------------------------------------------- + + /** + * Interleaves two iterables into a single iterable. + *

+ * The returned iterable has an iterator that traverses the elements in {@code a} + * and {@code b} in alternating order. The source iterators are not polled until + * necessary. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable + * @param b the second iterable + * @return a new iterable, interleaving the provided iterables + */ + @SuppressWarnings("unchecked") + public static Iterable zippingIterable(final Iterable a, final Iterable b) { + return zippingIterable(new Iterable[] {a, b}); + } + + /** + * Interleaves two iterables into a single iterable. + *

+ * The returned iterable has an iterator that traverses the elements in {@code a} + * and {@code b} in alternating order. The source iterators are not polled until + * necessary. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param a the first iterable + * @param b the second iterable + * @return a new iterable, interleaving the provided iterables + */ + public static Iterable zippingIterable(final Iterable... iterables) { + return new FluentIterable() { + @Override + public Iterator iterator() { + @SuppressWarnings("unchecked") + Iterator[] iterators = new Iterator[iterables.length]; + for (int i = 0; i < iterables.length; i++) { + iterators[i] = emptyIteratorIfNull(iterables[i]); + } + return new ZippingIterator(iterators); + } + }; + } + // Utility methods // ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java b/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java new file mode 100644 index 000000000..21a4aa3ff --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.collections4.iterators; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.FluentIterable; + +/** + * Provides an interleaved iteration over the elements contained in a + * collection of Iterators. + *

+ * Given two {@link Iterator} instances A and + * B, the {@link #next} method on this iterator will + * alternate between A.next() and B.next(). + * + * @since 4.1 + * @version $Id$ + */ +public class ZippingIterator implements Iterator { + + /** The {@link Iterator}s to evaluate. */ + private final Iterator> iterators; + /** The next iterator to use for next(). */ + private Iterator nextIterator = null; + /** The last iterator which was used for next(). */ + private Iterator lastReturned = null; + + // Constructors + // ---------------------------------------------------------------------- + + /** + * Constructs a new ZippingIterator that will provide + * interleaved iteration over the two given iterators. + * + * @param a the first child iterator + * @param b the second child iterator + * @throws NullPointerException if either iterator is null + */ + @SuppressWarnings("unchecked") + public ZippingIterator(final Iterator a, final Iterator b) { + this(new Iterator[] {a, b}); + } + + /** + * Constructs a new ZippingIterator that will use the + * specified comparator to provide ordered iteration over the array of + * iterators. + * + * @param iterators the array of iterators + * @throws NullPointerException if iterators array is or contains null + */ + public ZippingIterator(final Iterator... iterators) { + // create a mutable list + final List> list = new ArrayList>(); + for (Iterator iterator : iterators) { + if (iterator == null) { + throw new NullPointerException("Iterator must not be null"); + } + list.add(iterator); + } + this.iterators = FluentIterable.of(list).loop().iterator(); + } + + // Iterator Methods + // ------------------------------------------------------------------- + + /** + * Returns true if any child iterator has remaining elements. + * + * @return true if this iterator has remaining elements + */ + public boolean hasNext() { + // the next iterator has already been determined + // this might happen if hasNext() was called multiple + if (nextIterator != null) { + return true; + } + + while(iterators.hasNext()) { + final Iterator iterator = iterators.next(); + if (iterator.hasNext()) { + nextIterator = iterator; + return true; + } else { + // iterator is exhausted, remove it + iterators.remove(); + } + } + return false; + } + + /** + * Returns the next element from a child iterator. + * + * @return the next interleaved element + * @throws NoSuchElementException if no child iterator has any more elements + */ + public E next() throws NoSuchElementException { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final E val = nextIterator.next(); + lastReturned = nextIterator; + nextIterator = null; + return val; + } + + /** + * Removes the last returned element from the child iterator that produced it. + * + * @throws IllegalStateException if there is no last returned element, or if + * the last returned element has already been removed + */ + public void remove() { + if (lastReturned == null) { + throw new IllegalStateException("No value can be removed at present"); + } + lastReturned.remove(); + lastReturned = null; + } + +}