diff --git a/CHANGELOG.md b/CHANGELOG.md index f09e2f8..5dc82cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 0.6.0 + Implement `dropLast(n)` ++ Add support for `zipWith(collection)` and `zipWith(iterator)` ### 0.5.0 + Implement `reverse()` diff --git a/README.md b/README.md index ed8cfab..e66d9b1 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ implementation("com.ginsberg:gatherers4j:0.6.0") | `shuffle(rg)` | Shuffle the stream into a random order using the specified `RandomGenerator` | | `throttle(amount, duration)` | Limit stream elements to `amount` elements over `duration`, pausing until a new `duration` period starts | | `withIndex()` | Maps all elements of the stream as-is along with their 0-based index | +| `zipWith(collection)` | Creates a stream of `Pair` objects whose values come from the input stream and argument collection | +| `zipWith(iterator)` | Creates a stream of `Pair` objects whose values come from the input stream and argument iterator | | `zipWith(stream)` | Creates a stream of `Pair` objects whose values come from the input stream and argument stream | | `zipWithNext()` | Creates a stream of `List` objects via a sliding window of width 2 and stepping 1 | diff --git a/src/main/java/com/ginsberg/gatherers4j/Gatherers4j.java b/src/main/java/com/ginsberg/gatherers4j/Gatherers4j.java index 167920f..6d0dac2 100644 --- a/src/main/java/com/ginsberg/gatherers4j/Gatherers4j.java +++ b/src/main/java/com/ginsberg/gatherers4j/Gatherers4j.java @@ -18,6 +18,8 @@ import java.math.BigDecimal; import java.time.Duration; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.function.BiPredicate; import java.util.function.Function; @@ -343,7 +345,25 @@ public static IndexingGatherer withIndex() { } /** - * Creates a stream of `Pair` objects whose values come from the input stream and argument stream + * Creates a stream of `Pair` objects whose values come from the stream this is called on and the argument collection + * + * @param other A non-null collection to zip with + */ + public static Gatherer> zipWith(final Collection other) { + return new ZipWithGatherer<>(other); + } + + /** + * Creates a stream of `Pair` objects whose values come from the stream this is called on and the argument iterator + * + * @param other A non-null iterator to zip with + */ + public static Gatherer> zipWith(final Iterator other) { + return new ZipWithGatherer<>(other); + } + + /** + * Creates a stream of `Pair` objects whose values come from the stream this is called on and the argument stream * * @param other A non-null stream to zip with */ @@ -351,6 +371,7 @@ public static Gatherer> zipWith return new ZipWithGatherer<>(other); } + /** * Creates a stream of `List` objects via a sliding window of width 2 and stepping 1 */ diff --git a/src/main/java/com/ginsberg/gatherers4j/ZipWithGatherer.java b/src/main/java/com/ginsberg/gatherers4j/ZipWithGatherer.java index f0f4daf..f02892b 100644 --- a/src/main/java/com/ginsberg/gatherers4j/ZipWithGatherer.java +++ b/src/main/java/com/ginsberg/gatherers4j/ZipWithGatherer.java @@ -16,16 +16,31 @@ package com.ginsberg.gatherers4j; -import java.util.Objects; +import java.util.Collection; +import java.util.Iterator; import java.util.Spliterator; import java.util.stream.Gatherer; import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static com.ginsberg.gatherers4j.GathererUtils.mustNotBeNull; public class ZipWithGatherer implements Gatherer> { private final Spliterator otherSpliterator; + ZipWithGatherer(final Collection other) { + mustNotBeNull(other, "Other collection must not be null"); + this(other.stream()); + } + + ZipWithGatherer(final Iterator other) { + mustNotBeNull(other, "Other iterator must not be null"); + final Iterable iterable = () -> other; + this(StreamSupport.stream(iterable.spliterator(), false)); + } + ZipWithGatherer(final Stream other) { - Objects.requireNonNull(other, "Other stream must not be null"); + mustNotBeNull(other, "Other stream must not be null"); otherSpliterator = other.spliterator(); } @@ -33,7 +48,7 @@ public class ZipWithGatherer implements Gatherer> integrator() { return (_, element, downstream) -> otherSpliterator .tryAdvance( - it -> downstream.push(new Pair<>(element, it)) + it -> downstream.push(new Pair<>(element, it)) ) && !downstream.isRejecting(); } } diff --git a/src/test/java/com/ginsberg/gatherers4j/ZipWithGathererTest.java b/src/test/java/com/ginsberg/gatherers4j/ZipWithGathererTest.java index 5eef0ed..4aa1512 100644 --- a/src/test/java/com/ginsberg/gatherers4j/ZipWithGathererTest.java +++ b/src/test/java/com/ginsberg/gatherers4j/ZipWithGathererTest.java @@ -18,19 +18,73 @@ import org.junit.jupiter.api.Test; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class ZipWithGathererTest { @Test - void zipGatherer() { + void inputCollectionMustNotBeNull() { + assertThatThrownBy(() -> Stream.of("A") + .gather(Gatherers4j.zipWith((Collection)null)).toList() + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void inputIteratorMustNotBeNull() { + assertThatThrownBy(() -> Stream.of("A") + .gather(Gatherers4j.zipWith((Iterator)null)).toList() + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void inputStreamMustNotBeNull() { + assertThatThrownBy(() -> Stream.of("A") + .gather(Gatherers4j.zipWith((Stream)null)).toList() + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void interleavingGathererThisEmpty() { // Arrange - final Stream left = Stream.of("A", "B", "C"); + final Stream left = Stream.empty(); final Stream right = Stream.of(1, 2, 3); + // Act + final List> output = left + .gather(Gatherers4j.zipWith(right)) + .toList(); + + // Assert + assertThat(output).isEmpty(); + } + + @Test + void zipGathererOtherEmpty() { + // Arrange + final Stream left = Stream.of("A", "B", "C"); + final Stream right = Stream.empty(); + + // Act + final List> output = left + .gather(Gatherers4j.zipWith(right)) + .toList(); + + // Assert + assertThat(output).isEmpty(); + } + + @Test + void zipWithCollectionGatherer() { + // Arrange + final Stream left = Stream.of("A", "B", "C"); + final Collection right = List.of(1, 2, 3); + // Act final List> output = left .gather(Gatherers4j.zipWith(right)) @@ -46,10 +100,10 @@ void zipGatherer() { } @Test - void zipGathererOtherEmpty() { + void zipWithIteratorGatherer() { // Arrange final Stream left = Stream.of("A", "B", "C"); - final Stream right = Stream.empty(); + final Iterator right = List.of(1, 2, 3).iterator(); // Act final List> output = left @@ -57,13 +111,18 @@ void zipGathererOtherEmpty() { .toList(); // Assert - assertThat(output).isEmpty(); + assertThat(output) + .containsExactly( + new Pair<>("A", 1), + new Pair<>("B", 2), + new Pair<>("C", 3) + ); } @Test - void interleavingGathererThisEmpty() { + void zipWithStreamGatherer() { // Arrange - final Stream left = Stream.empty(); + final Stream left = Stream.of("A", "B", "C"); final Stream right = Stream.of(1, 2, 3); // Act @@ -72,6 +131,11 @@ void interleavingGathererThisEmpty() { .toList(); // Assert - assertThat(output).isEmpty(); + assertThat(output) + .containsExactly( + new Pair<>("A", 1), + new Pair<>("B", 2), + new Pair<>("C", 3) + ); } } \ No newline at end of file