5

I'm learning Java 8 lambdas and streams. So, I got an array of lists having varying lengths. Lists contain integers.

What would be the best way to collect vertical slices in another list of lists i.e collect all integers with index 0 from all original lists in slice 0, index 1 in slice 1 and so an until the length of the longest list (filling zeros for shorter lists)

I know it's trivial to hand code a couple of traditional loops for this but what about doing this using Java 8 features?

2 Answers 2

4

That's a pretty interesting question - thanks for posting. I'm sure you'll see some interesting answers. Here's my attempt:

List<Integer> source[];
List<List<Integer>> slices = IntStream.range(0, Arrays.stream(source).mapToInt(List::size).max().getAsInt())
     .mapToObj(index -> Arrays.stream(source).map(list -> list.size() > index ? list.get(index) : 0)
         .collect(Collectors.toList()))
     .collect(Collectors.toList())
Sign up to request clarification or add additional context in comments.

5 Comments

It doesn't compile. The compiler error for max() is required: Comparator<? super Integer>.
@SubOptimal: I took the freedom to fix the compiler error as the solution should work in general.
Oops forgot to map to int. Thanks for fixing Holger.
@sprinter got the code, now what about "inverted" solution i.e. which in the first line goes through stream of lists in the source array and via a running index collects values into slices from each list. I'm not sure firing up internal stream all over again on each index in IntStream.range is very effective.
@tshymbra: if you want the “inverted solution”, look at my answer. But it doesn’t matter how many Streams are created as it’s not the stream itself that matters as a Stream is only a lightweight object. What matters is that it is iterating over the source again for every index, hence performs max list size × number of lists operations but that’s unavoidable. In theory, the order of iterations might have an implication on cache usage but the effects are not really predictable and might differ from application to application. So recall, “optimize only if you have real performance problems”
2

Here is a solution which does not insert padding values for shorter Lists:

List<List<Integer>> slices = Stream.of(source).flatMap(l->
    IntStream.range(0, l.size()).mapToObj(i->new int[]{i, l.get(i)})
).collect(collectingAndThen(
    groupingBy(a->a[0], TreeMap::new, mapping(a->a[1], toList())),
    m->new ArrayList<>(m.values()))
);

this can be expanded to a zero-padding version like this:

int maxSize=IntStream.range(0,source.length).map(i->source[i].size()).max().orElse(0);
List<List<Integer>> slices = Stream.of(source).flatMap(l->
    Stream.concat(
      IntStream.range(0, l.size()).mapToObj(i->new int[]{i, l.get(i)}),
      IntStream.range(l.size(), maxSize).mapToObj(i->new int[]{i, 0})
    )
).collect(collectingAndThen(
    groupingBy(a->a[0], TreeMap::new, mapping(a->a[1], toList())),
    m->new ArrayList<>(m.values()))
);

Both solutions assume that you do import static java.util.stream.Collectors.*; as otherwise the code becomes really unreadable.

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.