Functional unzip implementation for potentially infinite streams

Viewed 324

I've implemented functional unzip() operation as follows:

public static <T, U, V> Tuple2<Stream<U>, Stream<V>> unzip(
        Stream<T> stream, 
        Function<T, Tuple2<U, V>> unzipper) {

    return stream.map(unzipper)
        .reduce(new Tuple2<>(Stream.<U>empty(), Stream.<V>empty()),
            (unzipped, tuple) -> new Tuple2<>(
                Stream.concat(unzipped.$1(), Stream.of(tuple.$1())),
                Stream.concat(unzipped.$2(), Stream.of(tuple.$2()))),
            (unzipped1, unzipped2) -> new Tuple2<>(
                Stream.concat(unzipped1.$1(), unzipped2.$1()),
                Stream.concat(unzipped1.$2(), unzipped2.$2())));
}

This works fine, given input streams don't have a lot of elements. This is because accessing an element of a deeply concatenated stream can cause StackOverflowException. According to the docs of Stream.concat():

Implementation Note:

Use caution when constructing streams from repeated concatenation. Accessing an element of a deeply concatenated stream can result in deep call chains, or even StackOverflowException.

For few elements, my unzip implementation works. Given a class Person:

class Person {

    public final String name;

    public final int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

If I have a stream of people:

Stream<Person> people = Stream.of(
    new Person("Joe", 52), 
    new Person("Alan", 34), 
    new Person("Peter", 42));

I can use my unzip() implementation this way:

Tuple2<Stream<String>, Stream<Integer>> result = StreamUtils.unzip(people, 
        person -> new Tuple2<>(person.name, person.age));

List<String> names = result.$1()
    .collect(Collectors.toList()); // ["Joe", "Alan", "Peter"]
List<Integer> ages = result.$2()
    .collect(Collectors.toList()); // [52, 34, 42]

Which is correct.

So my question is: is there a way for unzip() to work with many elements (potentially infinite)?

Note: for completeness, here's my immutable Tuple2 class:

public final class Tuple2<A, B> {

    private final A $1;

    private final B $2;

    public Tuple2(A $1, B $2) {
        this.$1 = $1;
        this.$2 = $2;
    }

    public A $1() {
        return $1;
    }

    public B $2() {
        return $2;
    }
}
1 Answers
Related