5

I'm trying Java 8, I want to iterate over 2 collections and call a parameter function for each pair of values.

In abstract, I want to apply a foo(tuple, i) function for each iteration

[ v1, v2, v3, v4, v5, v6 ] (first collection)
[ w1, w2, w3, w4, w5, w6 ] (second collection)
---------------------------
  foo(<v1,w1>, 0)
  foo(<v2,w2>, 1)
  ...
  foo(<v6,w6>, 5)

Now what I got so far (java and pseudo code)

// Type of f?
private <S,U> void iterateSimultaneously(Collection<S> c1, Collection<U> c2, Function f) {
        int i = 0
        Iterator<S> it1 = c1.iterator()
        Iterator<U> it2 = c2.iterator()
        while(it1.hasNext() && it2.hasNext()) {
            Tuple<S, U> tuple = new Tuple<>(it1.next(), it2.next())             

            // call somehow f(tuple, i)

            i++
        }
}
 // ........................

// pseudo code, is this posible in Java?
iterateSimultaneously(c1, c2, (e1, e2, i) -> {
  // play with those items and the i value
})
5
  • What does the comment // Type of f? mean? What are e1, e2, i? Commented May 16, 2017 at 10:05
  • Are all collections iterated over going to be the same size, or should different sizes be handled? Commented May 16, 2017 at 10:05
  • 1
    Take a look at stream "zipping": stackoverflow.com/questions/17640754/… Commented May 16, 2017 at 10:13
  • 1
    It’s funny, how often people on Stackoverflow need to iterate two collections simultaneously while I never encountered that task in real life. Commented May 16, 2017 at 10:34
  • @Aaron Let's say they have the same size Commented May 16, 2017 at 10:51

4 Answers 4

4

Is something like this what you're looking for?

private <S,U> void iterateSimultaneously(Collection<S> c1, Collection<U> c2, BiConsumer<Tuple<S, U>, Integer> f) {
        int i = 0
        Iterator<S> it1 = c1.iterator()
        Iterator<U> it2 = c2.iterator()
        while(it1.hasNext() && it2.hasNext()) {
            Tuple<S, U> tuple = new Tuple<>(it1.next(), it2.next())             
            f.accept(tuple, i);
            i++
        }
}
iterateSimultaneously(c1, c2, (t, i) -> {
    //stuff
})

What type is the function f supposed to return? If nothing, change it to a consumer instead. If you want it to accept a tuple you most clarify it like I have done here. Is this what you're looking for?

Sign up to request clarification or add additional context in comments.

Comments

4

You are probably looking for a BiConsumer:

private <S,U> void iterateSimultaneously(Collection<S> c1, Collection<U> c2,
                                         BiConsumer<Tuple<S, U>, Integer> f) {

  f.accept(tuple, i);
}

and call it with:

iterateSimultaneously(c1, c2, (tuple, i) -> doSomethingWith(tuple, i));

The signature of doSomethingWith would look like:

private <S, U> void doSomethingWith(Tuple<S, U> tuple, int i) {

}

6 Comments

That looks great, but I fail to see how it works. Also those tuple and i var in iterateSimultaneously doesn't seem to correspond to anything ?
@Aaron not sure I understand your question.
You write f.accept(tuple, i); in your first snippet, but I fail to see how tuple and i are defined in this scope.
What is this Tuple class?
@user1803551 it's just a Pair or a Holder for two values; like a Map.Entry
|
2

You can find an detailed implementation using Stream API of Java 8 of what you are looking for just here (the method zip()) : https://github.com/JosePaumard/streams-utils/blob/master/src/main/java/org/paumard/streams/StreamsUtils.java#L398

Comments

0

Take a look at Guava's utilities for streams, particularly Streams.zip and Streams.mapWithIndex. You might use them both to achieve what you want:

Collection<Double> numbers = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
Collection<String> letters = Arrays.asList("a", "b", "c", "d", "e");

Stream<Tuple<Double, String>> zipped = Streams.zip(
    numbers.stream(),
    letters.stream(),
    Tuple::new);

Stream<String> withIndex = Streams.mapWithIndex(
    zipped, 
    (tuple, index) -> index + ": " + tuple.u + "/" + tuple.v);

withIndex.forEach(System.out::println);

This produces the following output:

0: 1.1/a
1: 2.2/b
2: 3.3/c
3: 4.4/d
4: 5.5/e

This works by first zipping streams for c1 and c2 collections into one zipped stream of tuples and then mapping this zipped stream with a function that receives both each tuple and its corresponding index.


Note that Streams.mapWithIndex must receive a BiFunction, which means that it must return a value. If you want to consume both the tuples and the indices instead, I'm afraid you will need to create a new tuple containing the original tuple and the index:

Stream<Tuple<Tuple<Double, String>, Long>> withIndex = Streams.mapWithIndex(
    zipped,
    Tuple::new);

withIndex.forEach(tuple -> someMethod(tuple.u, tuple.v));

Where someMethod has the following signature:

<U, V> void method(Tuple<U, V> tuple, long index)

Note 1: this example assumes the following Tuple class is used:

public class Tuple<U, V> {
    private final U u;
    private final V v;

    Tuple(U u, V v) {
        this.u = u;
        this.v = v;
    }

    // TODO: getters and setters, hashCode and equals
}

Note 2: while you can achieve the same with iterators, the main advantage of these utilities is that they also work efficiently on parallel streams.

Note 3: this functionality is available since Guava 21.0.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.