Skip to content

Commit 8f1a088

Browse files
kevinbcpovirk
authored andcommitted
(w/lowasser) Streams.forEachPair()
[] ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151386365
1 parent f24328a commit 8f1a088

File tree

2 files changed

+108
-2
lines changed

2 files changed

+108
-2
lines changed

guava-tests/test/com/google/common/collect/StreamsTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.OptionalDouble;
3232
import java.util.OptionalInt;
3333
import java.util.OptionalLong;
34+
import java.util.concurrent.atomic.AtomicInteger;
3435
import java.util.function.Function;
3536
import java.util.stream.Collectors;
3637
import java.util.stream.DoubleStream;
@@ -264,6 +265,54 @@ public void testZipDifferingLengths() {
264265
.inOrder();
265266
}
266267

268+
public void testForEachPair() {
269+
List<String> list = new ArrayList<>();
270+
Streams.forEachPair(
271+
Stream.of("a", "b", "c"), Stream.of(1, 2, 3), (a, b) -> list.add(a + ":" + b));
272+
Truth.assertThat(list).containsExactly("a:1", "b:2", "c:3");
273+
}
274+
275+
public void testForEachPair_differingLengths1() {
276+
List<String> list = new ArrayList<>();
277+
Streams.forEachPair(
278+
Stream.of("a", "b", "c", "d"), Stream.of(1, 2, 3), (a, b) -> list.add(a + ":" + b));
279+
Truth.assertThat(list).containsExactly("a:1", "b:2", "c:3");
280+
}
281+
282+
public void testForEachPair_differingLengths2() {
283+
List<String> list = new ArrayList<>();
284+
Streams.forEachPair(
285+
Stream.of("a", "b", "c"), Stream.of(1, 2, 3, 4), (a, b) -> list.add(a + ":" + b));
286+
Truth.assertThat(list).containsExactly("a:1", "b:2", "c:3");
287+
}
288+
289+
public void testForEachPair_oneEmpty() {
290+
Streams.forEachPair(Stream.of("a"), Stream.empty(), (a, b) -> fail());
291+
}
292+
293+
public void testForEachPair_finiteWithInfinite() {
294+
List<String> list = new ArrayList<>();
295+
Streams.forEachPair(
296+
Stream.of("a", "b", "c"), Stream.iterate(1, i -> i + 1), (a, b) -> list.add(a + ":" + b));
297+
Truth.assertThat(list).containsExactly("a:1", "b:2", "c:3");
298+
}
299+
300+
public void testForEachPair_parallel() {
301+
Stream<String> streamA = IntStream.range(0, 100000).mapToObj(String::valueOf).parallel();
302+
Stream<Integer> streamB = IntStream.range(0, 100000).mapToObj(i -> i).parallel();
303+
304+
AtomicInteger count = new AtomicInteger(0);
305+
Streams.forEachPair(
306+
streamA,
307+
streamB,
308+
(a, b) -> {
309+
count.incrementAndGet();
310+
Truth.assertThat(a.equals(String.valueOf(b))).isTrue();
311+
});
312+
Truth.assertThat(count.get()).isEqualTo(100000);
313+
// of course, this test doesn't prove that anything actually happened in parallel...
314+
}
315+
267316
// TODO(kevinb): switch to importing Truth's assertThat(Stream) if we get that added
268317
private static IterableSubject assertThat(Stream<?> stream) {
269318
return Truth.assertThat(stream.toArray()).asList();

guava/src/com/google/common/collect/Streams.java

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Spliterator;
3434
import java.util.Spliterators;
3535
import java.util.Spliterators.AbstractSpliterator;
36+
import java.util.function.BiConsumer;
3637
import java.util.function.BiFunction;
3738
import java.util.function.Consumer;
3839
import java.util.function.DoubleConsumer;
@@ -205,7 +206,7 @@ public static DoubleStream concat(DoubleStream... streams) {
205206
}
206207

207208
/**
208-
* Returns a stream in which each element is the result of passing the corresponding element of
209+
* Returns a stream in which each element is the result of passing the corresponding elementY of
209210
* each of {@code streamA} and {@code streamB} to {@code function}.
210211
*
211212
* <p>For example:
@@ -222,7 +223,10 @@ public static DoubleStream concat(DoubleStream... streams) {
222223
* <p>The resulting stream will only be as long as the shorter of the two input streams; if one
223224
* stream is longer, its extra elements will be ignored.
224225
*
225-
* <p>The resulting stream is not <a
226+
* <p>Note that if you are calling {@link Stream#forEach} on the resulting stream, you might want
227+
* to consider using {@link #forEachPair} instead of this method.
228+
*
229+
* <p><b>Performance note:</b> The resulting stream is not <a
226230
* href="http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html">efficiently splittable</a>.
227231
* This may harm parallel performance.
228232
*/
@@ -255,6 +259,59 @@ public boolean tryAdvance(Consumer<? super R> action) {
255259
isParallel);
256260
}
257261

262+
/**
263+
* Invokes {@code consumer} once for each pair of <i>corresponding</i> elements in {@code streamA}
264+
* and {@code streamB}. If one stream is longer than the other, the extra elements are silently
265+
* ignored. Elements passed to the consumer are guaranteed to come from the same position in their
266+
* respective source streams. For example:
267+
*
268+
* <pre>{@code
269+
* Streams.forEachPair(
270+
* Stream.of("foo1", "foo2", "foo3"),
271+
* Stream.of("bar1", "bar2"),
272+
* (arg1, arg2) -> System.out.println(arg1 + ":" + arg2)
273+
* }</pre>
274+
*
275+
* <p>will print:
276+
*
277+
* <pre>{@code
278+
* foo1:bar1
279+
* foo2:bar2
280+
* }</pre>
281+
*
282+
* <p><b>Warning:</b> If either supplied stream is a parallel stream, the same correspondence
283+
* between elements will be made, but the order in which those pairs of elements are passed to the
284+
* consumer is <i>not</i> defined.
285+
*
286+
* <p>Note that many usages of this method can be replaced with simpler calls to {@link #zip}.
287+
* This method behaves equivalently to {@linkplain #zip zipping} the stream elements into
288+
* temporary pair objects and then using {@link Stream#forEach} on that stream.
289+
*/
290+
public static <A, B> void forEachPair(
291+
Stream<A> streamA, Stream<B> streamB, BiConsumer<? super A, ? super B> consumer) {
292+
checkNotNull(consumer);
293+
294+
if (streamA.isParallel() || streamB.isParallel()) {
295+
zip(streamA, streamB, TemporaryPair::new).forEach(pair -> consumer.accept(pair.a, pair.b));
296+
} else {
297+
Iterator<A> iterA = streamA.iterator();
298+
Iterator<B> iterB = streamB.iterator();
299+
while (iterA.hasNext() && iterB.hasNext()) {
300+
consumer.accept(iterA.next(), iterB.next());
301+
}
302+
}
303+
}
304+
305+
private static class TemporaryPair<A, B> {
306+
final A a;
307+
final B b;
308+
309+
TemporaryPair(A a, B b) {
310+
this.a = a;
311+
this.b = b;
312+
}
313+
}
314+
258315
/**
259316
* Returns a stream consisting of the results of applying the given function to the elements of
260317
* {@code stream} and their indices in the stream. For example,

0 commit comments

Comments
 (0)