zippingIterator(final Iterator extends E>
return new ZippingIterator<>(iterators);
}
+ /**
+ * Gets an Iterator over the elements contained in a pair of Iterables in-tandem.
+ *
+ * The returned iterator traverses the elements in {@code a} and {@code b} together until one of the iterators
+ * is exhausted.
+ *
+ * The returned iterator does NOT support {@code remove()}.
+ *
+ * @param the left elements' type
+ * @param the right elements' type
+ * @param left the iterator for the left side elements
+ * @param right the iterator for the right side elements
+ * @return an iterator, to iterate over the decorated iterators together until one is exhausted
+ * @throws NullPointerException if any iterator is null
+ */
+ public static PairedIterator pairedIterator(final Iterator left, Iterator right) {
+ return PairedIterator.of(left, right);
+ }
+
// Views
/**
* Gets an iterator that provides an iterator view of the given enumeration.
diff --git a/src/main/java/org/apache/commons/collections4/PairedIterable.java b/src/main/java/org/apache/commons/collections4/PairedIterable.java
new file mode 100644
index 0000000000..2d0559558b
--- /dev/null
+++ b/src/main/java/org/apache/commons/collections4/PairedIterable.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import org.apache.commons.collections4.iterators.PairedIterator;
+import org.apache.commons.collections4.iterators.PairedIterator.PairedItem;
+
+import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Provides iteration over the elements contained in a pair of Iterables in-tandem.
+ *
+ *
+ * Given two {@link Iterable} instances {@code A} and {@code B}, the {@link #iterator} method on this
+ * iterator provide a Pair of {@code A.next()} and {@code B.next()} until one of the iterators is
+ * exhausted.
+ *
+ * This can simplify zipping over two iterables using the for-each construct.
+ * Example usage:
+ * {@code
+ * List studentIds = ...
+ * List studentNames = ...
+ *
+ * for (PairedItem items : PairedIterable.of(studentIds, studentNames) {
+ * Integer studentId = item.getLeft();
+ * String studentName = item.getRight();
+ * ...
+ * }
+ * }
+ *
+ * @param the left elements' type
+ * @param the right elements' type
+ */
+public class PairedIterable implements Iterable> {
+
+ /**
+ * The left {@link Iterable}s to evaluate.
+ */
+ private final Iterable leftIterable;
+
+ /**
+ * The right {@link Iterable}s to evaluate.
+ */
+ private final Iterable rightIterable;
+
+ // Constructor
+ // ----------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@code PairedIterable} that will provide iteration over two given iterables.
+ *
+ * @param leftIterable the iterable for the left side element.
+ * @param rightIterable the iterable for the right side element.
+ * @throws NullPointerException if either iterator is null
+ */
+ public PairedIterable(Iterable leftIterable, Iterable rightIterable) {
+ this.leftIterable = requireNonNull(leftIterable);
+ this.rightIterable = requireNonNull(rightIterable);
+ }
+
+ /**
+ * Convenience static factory to construct the PairedIterable from provided
+ * {@link Iterable} sources.
+ *
+ * @param leftIterable the iterable for the left side element.
+ * @param rightIterable the iterable for the right side element.
+ * @return the Iterable to iterate over the elements derived from the provided iterables.
+ * @throws NullPointerException if either iterables is null
+ */
+ public static PairedIterable of(Iterable leftIterable, Iterable rightIterable) {
+ return new PairedIterable<>(leftIterable, rightIterable);
+ }
+
+ // Iterable Methods
+ // -------------------------------------------------------------------
+
+ @Override
+ public Iterator> iterator() {
+ return PairedIterator.ofIterables(leftIterable, rightIterable);
+ }
+
+ public Stream> stream() {
+ return StreamSupport.stream(spliterator(), /*parallel=*/ false);
+ }
+}
diff --git a/src/main/java/org/apache/commons/collections4/iterators/PairedIterator.java b/src/main/java/org/apache/commons/collections4/iterators/PairedIterator.java
new file mode 100644
index 0000000000..044056058a
--- /dev/null
+++ b/src/main/java/org/apache/commons/collections4/iterators/PairedIterator.java
@@ -0,0 +1,171 @@
+/*
+ * 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 static java.util.Objects.requireNonNull;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.apache.commons.collections4.iterators.PairedIterator.PairedItem;
+
+/**
+ * Provides a iteration over the elements contained in a pair of Iterators.
+ *
+ *
+ * Given two {@link Iterator} instances {@code A} and {@code B}, the {@link #next} method on this
+ * iterator provide a Pair of {@code A.next()} and {@code B.next()} until one of the iterators is
+ * exhausted.
+ *
+ * Example usage:
+ * {@code
+ * List studentIds = ...
+ * List studentNames = ...
+ *
+ * PairedIterator> pairedIterator =
+ * PairedIterator.ofIterables(studentIds, studentNames);
+ *
+ * while (pairedIterator.hasNext()) {
+ * PairedItem item = zippedIterator.next();
+ * ...
+ * }
+ * }
+ *
+ * @param the left elements' type
+ * @param the right elements' type
+ */
+public class PairedIterator implements Iterator> {
+
+ /**
+ * The left {@link Iterator}s to evaluate.
+ */
+ private final Iterator leftIterator;
+
+ /**
+ * The right {@link Iterator}s to evaluate.
+ */
+ private final Iterator rightIterator;
+
+ // Constructor
+ // ----------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@code ZipPairIterator} that will provide iteration over the two given
+ * iterators.
+ *
+ * @param leftIterator the iterator for the left side element.
+ * @param rightIterator the iterator for the right side element.
+ * @throws NullPointerException if either iterator is null
+ */
+ public PairedIterator(Iterator leftIterator, Iterator rightIterator) {
+ this.leftIterator = requireNonNull(leftIterator);
+ this.rightIterator = requireNonNull(rightIterator);
+ }
+
+ /**
+ * Convenience static factory to construct the ZipPairIterator
+ *
+ * @param leftIterator the iterator for the left side element.
+ * @param rightIterator the iterator for the right side element.
+ * @return the iterator to iterate over the provided iterators.
+ * @throws NullPointerException if either iterator is null
+ */
+ public static PairedIterator of(Iterator leftIterator, Iterator rightIterator) {
+ return new PairedIterator<>(leftIterator, rightIterator);
+ }
+
+ /**
+ * Convenience static factory to construct the ZipPairIterator from any {@link Iterable} sources.
+ *
+ * @param leftIterable the iterable for the left side element.
+ * @param rightIterable the iterable for the right side element.
+ * @return the iterator to iterate over the iterators derived from the provided iterables.
+ * @throws NullPointerException if either iterables is null
+ */
+ public static PairedIterator ofIterables(Iterable leftIterable, Iterable rightIterable) {
+ return of(requireNonNull(leftIterable).iterator(), requireNonNull(rightIterable).iterator());
+ }
+
+ // Iterator Methods
+ // -------------------------------------------------------------------
+
+ /**
+ * Returns {@code true} if both the child iterators have remaining elements.
+ *
+ * @return true if both the child iterators have remaining elements
+ */
+ @Override
+ public boolean hasNext() {
+ return leftIterator.hasNext() && rightIterator.hasNext();
+ }
+
+ /**
+ * Returns the next elements from both the child iterators.
+ *
+ * @return the next elements from both the iterators.
+ * @throws NoSuchElementException if any one child iterator is exhausted.
+ */
+ @Override
+ public PairedItem next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ return PairedItem.of(leftIterator.next(), rightIterator.next());
+ }
+
+ /**
+ * An immutable tuple class to represent elements from both the iterators.
+ *
+ * @param the left elements' type
+ * @param the right elements' type
+ */
+ public static final class PairedItem {
+
+ private final L leftItem;
+
+ private final R rightItem;
+
+ private PairedItem(L leftItem, R rightItem) {
+ this.leftItem = leftItem;
+ this.rightItem = rightItem;
+ }
+
+ /**
+ * Convenience static factory method to construct the tuple pair.
+ *
+ * @param left the left element
+ * @param right the right element
+ * @return the Immutable tuple pair of two elements.
+ */
+ private static PairedItem of(L left, R right) {
+ return new PairedItem<>(left, right);
+ }
+
+ public L getLeftItem() {
+ return leftItem;
+ }
+
+ public R getRightItem() {
+ return rightItem;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s, %s}", leftItem, rightItem);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/collections4/IteratorUtilsTest.java b/src/test/java/org/apache/commons/collections4/IteratorUtilsTest.java
index 01587f9f55..960bf9b341 100644
--- a/src/test/java/org/apache/commons/collections4/IteratorUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/IteratorUtilsTest.java
@@ -1116,4 +1116,11 @@ public void testZippingIterator() {
assertTrue(IteratorUtils.zippingIterator(ie, ie) instanceof ZippingIterator, "create instance fail");
}
+ @Test
+ public void testPairedIterator() {
+ final ArrayList stringList = new ArrayList<>();
+ final ArrayList integerList = new ArrayList<>();
+
+ assertTrue(IteratorUtils.pairedIterator(stringList.iterator(), integerList.iterator()) instanceof PairedIterator, "create instance failed");
+ }
}
diff --git a/src/test/java/org/apache/commons/collections4/PairedIterableTest.java b/src/test/java/org/apache/commons/collections4/PairedIterableTest.java
new file mode 100644
index 0000000000..6a7c95c7c1
--- /dev/null
+++ b/src/test/java/org/apache/commons/collections4/PairedIterableTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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;
+
+import org.apache.commons.collections4.iterators.PairedIterator.PairedItem;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.*;
+import java.util.stream.IntStream;
+
+import static java.util.Collections.unmodifiableList;
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+@RunWith(Enclosed.class)
+public final class PairedIterableTest {
+
+ private static final int SMALL_LIST_SIZE = 20;
+ private static final int LARGE_LIST_SIZE = 40;
+
+ private static final List SMALL_STRINGS_LIST =
+ unmodifiableList(stringsList(SMALL_LIST_SIZE));
+ private static final List LARGE_STRINGS_LIST =
+ unmodifiableList(stringsList(LARGE_LIST_SIZE));
+ private static final List SMALL_INTS_LIST = unmodifiableList(intsList(SMALL_LIST_SIZE));
+ private static final List LARGE_INTS_LIST = unmodifiableList(intsList(LARGE_LIST_SIZE));
+
+ @RunWith(Parameterized.class)
+ public static final class ParameterizedTests {
+ private final String testCondition;
+ private final List leftList;
+ private final List rightList;
+ private final int expectedIterableSize;
+
+ public ParameterizedTests(
+ String testCondition, List leftList, List rightList, int expectedIterableSize) {
+ this.testCondition = testCondition;
+ this.leftList = leftList;
+ this.rightList = rightList;
+ this.expectedIterableSize = expectedIterableSize;
+ }
+
+ @Test
+ public void testAllowsForEach() {
+ ArrayList> outputPairedIterable = new ArrayList<>();
+ for (PairedItem item : PairedIterable.of(leftList, rightList)) {
+ outputPairedIterable.add(item);
+ }
+
+ assertEquals(expectedIterableSize, outputPairedIterable.size());
+
+ for (int i = 0; i < outputPairedIterable.size(); i++) {
+ PairedItem item = outputPairedIterable.get(i);
+ assertEquals(leftList.get(i), item.getLeftItem());
+ assertEquals(rightList.get(i), item.getRightItem());
+ }
+ }
+
+ @Test
+ public void testStream() {
+ PairedIterable testIterable = PairedIterable.of(leftList, rightList);
+
+ assertEquals(
+ leftList.subList(0, expectedIterableSize),
+ testIterable.stream().map(PairedItem::getLeftItem).collect(toList()));
+ assertEquals(
+ rightList.subList(0, expectedIterableSize),
+ testIterable.stream().map(PairedItem::getRightItem).collect(toList()));
+ }
+
+ @Parameters(name = "{0}")
+ public static Object[][] testingParameters() {
+ return new Object[][] {
+ new Object[] {
+ "left iterable (int) larger than right iterable (string)",
+ LARGE_INTS_LIST,
+ SMALL_STRINGS_LIST,
+ SMALL_LIST_SIZE
+ },
+ new Object[] {
+ "left iterable (string) larger than right iterable (int)",
+ LARGE_STRINGS_LIST,
+ SMALL_INTS_LIST,
+ SMALL_LIST_SIZE
+ },
+ new Object[] {
+ "equal sized left and right (int, string)",
+ LARGE_INTS_LIST,
+ LARGE_STRINGS_LIST,
+ LARGE_LIST_SIZE
+ },
+ new Object[] {
+ "equal sized left and right (string, int)",
+ SMALL_STRINGS_LIST,
+ SMALL_INTS_LIST,
+ SMALL_LIST_SIZE
+ },
+ new Object[] {
+ "Left empty, right small list", Collections.emptyList(), LARGE_STRINGS_LIST, 0
+ },
+ new Object[] {
+ "Right empty, left small list", LARGE_STRINGS_LIST, Collections.emptyList(), 0
+ },
+ new Object[] {
+ "Right and left both empty lists", Collections.emptyList(), Collections.emptyList(), 0
+ },
+ };
+ }
+ }
+
+ @RunWith(JUnit4.class)
+ public static final class ErrorTest {
+
+ @Test
+ public void leftIterableNullThrowsException() {
+ assertThrows(NullPointerException.class, () -> PairedIterable.of(null, SMALL_INTS_LIST));
+ }
+
+ @Test
+ public void rightIterableNullThrowsException() {
+ assertThrows(NullPointerException.class, () -> PairedIterable.of(SMALL_INTS_LIST, null));
+ }
+ }
+
+ private static List stringsList(int size) {
+ return IntStream.range(0, size)
+ .boxed()
+ .map(x -> UUID.randomUUID().toString())
+ .collect(toList());
+ }
+
+ private static List intsList(int size) {
+ Random rnd = new Random();
+ return IntStream.range(0, size).boxed().map(x -> rnd.nextInt()).collect(toList());
+ }
+}
diff --git a/src/test/java/org/apache/commons/collections4/iterators/PairedIteratorTest.java b/src/test/java/org/apache/commons/collections4/iterators/PairedIteratorTest.java
new file mode 100644
index 0000000000..aede97022a
--- /dev/null
+++ b/src/test/java/org/apache/commons/collections4/iterators/PairedIteratorTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.commons.collections4.iterators.PairedIterator.PairedItem;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.UUID;
+
+
+/** Unit test suite for {@link PairedIterator}. */
+public final class PairedIteratorTest
+ extends AbstractIteratorTest> {
+
+ public PairedIteratorTest() { super(ObjectArrayIteratorTest.class.getSimpleName()); }
+
+ private ArrayList smallStringsList;
+ private ArrayList largeStringsList;
+ private ArrayList smallIntsList;
+ private ArrayList largeIntsList;
+
+ private static final int SMALL_LIST_SIZE = 20;
+ private static final int LARGE_LIST_SIZE = 40;
+
+
+ @BeforeEach
+ public void setUp() throws Exception {
+
+ smallStringsList = new ArrayList<>();
+ largeStringsList = new ArrayList<>();
+ smallIntsList = new ArrayList<>();
+ largeIntsList = new ArrayList<>();
+
+ Random random = new Random();
+
+ for (int i = 0; i < SMALL_LIST_SIZE; i++) {
+ smallIntsList.add(random.nextInt());
+ smallStringsList.add(UUID.randomUUID().toString());
+ }
+
+ for (int i = 0; i < LARGE_LIST_SIZE; i++) {
+ largeIntsList.add(random.nextInt());
+ largeStringsList.add(UUID.randomUUID().toString());
+ }
+ }
+
+ @Override
+ public boolean supportsRemove() {
+ return false;
+ }
+
+ @Override
+ public Iterator> makeEmptyIterator() {
+ return PairedIterator.of(IteratorUtils.emptyIterator(), IteratorUtils.emptyIterator());
+ }
+
+ @Override
+ public Iterator> makeObject() {
+ return PairedIterator.of(smallStringsList.iterator(), smallIntsList.iterator());
+ }
+
+ @Test
+ public void testLeftIteratorLargerThanRight() {
+ Iterator> zipPairIterator =
+ PairedIterator.ofIterables(largeStringsList, smallIntsList);
+
+
+ for (int i = 0; i < SMALL_LIST_SIZE; i++) {
+ assertTrue(zipPairIterator.hasNext());
+ PairedItem zippedItem = zipPairIterator.next();
+
+ assertEquals(largeStringsList.get(i), zippedItem.getLeftItem());
+ assertEquals(smallIntsList.get(i), zippedItem.getRightItem());
+ }
+
+ assertFalse(zipPairIterator.hasNext());
+ }
+
+ @Test
+ public void testRightIteratorLargerThanLeft() {
+ Iterator> zipPairIterator =
+ PairedIterator.ofIterables(smallStringsList, largeIntsList);
+
+
+ for (int i = 0; i < SMALL_LIST_SIZE; i++) {
+ assertTrue(zipPairIterator.hasNext());
+ PairedItem zippedItem = zipPairIterator.next();
+
+ assertEquals(smallStringsList.get(i), zippedItem.getLeftItem());
+ assertEquals(largeIntsList.get(i), zippedItem.getRightItem());
+ }
+
+ assertFalse(zipPairIterator.hasNext());
+ }
+
+ @Test
+ public void testEmptyLeftIterator() {
+ Iterator> zipPairIterator =
+ PairedIterator.of(IteratorUtils.emptyIterator(), largeIntsList.iterator());
+
+ assertFalse(zipPairIterator.hasNext());
+ }
+
+ @Test
+ public void testEmptyRightIterator() {
+ Iterator> zipPairIterator =
+ PairedIterator.of(largeStringsList.iterator(), IteratorUtils.emptyIterator());
+
+ assertFalse(zipPairIterator.hasNext());
+ }
+
+ @Test
+ public void testValidTupleString() {
+ Iterator> zipPairIterator =
+ PairedIterator.ofIterables(smallStringsList, largeIntsList);
+
+
+ for (int i = 0; i < SMALL_LIST_SIZE; i++) {
+ assertTrue(zipPairIterator.hasNext());
+ PairedItem zippedItem = zipPairIterator.next();
+
+ assertEquals(
+ String.format("{%s, %s}", zippedItem.getLeftItem(), zippedItem.getRightItem()),
+ zippedItem.toString());
+ }
+
+ assertFalse(zipPairIterator.hasNext());
+ }
+}