Composing streams with flatmap in Java 8
Asked Answered
L

2

4

Let's consider I have the following class:

class A {
   int i, j, k;

   public A(int i, int j, int k) {
     this.i = i; this.j = j; this.k = k;
   }
}

where i, j, k have a known range: r_i, r_j, r_k. Now I want to to generate all possible instances of A in this range. I could come up with something like:

Stream.iterate(0, n -> ++n).limit(r_i)
.flatMap(i -> Stream.iterate(0, n -> ++n).limit(r_j)
.flatMap(j -> Stream.iterate(0, n -> ++n).limit(r_k)
.map(k -> new A(i, j, k)))).collect(Collectors.toList())

First, it's too verbose. Is there a way to shorten it? In particular I couldn't find a range on Stream. Second, the compiler cannot determine the type of the returned type. It considers it as List<Object> instead of the expected List<A>. How can I fix that?

Lentissimo answered 6/1, 2015 at 15:45 Comment(9)
You might want to look at IntStream.range to start with.Bauske
@JonSkeet I tried and it has other problems. The flatMap there doesn't allow arbitrary return type, only integer. There is an awkward mapToObj method, but I haven't found flatmapToObj.Lentissimo
Right. It pains me to see Java streams being awkward when LINQ in C# makes this sort of thing so simple :(Bauske
This can definitely be done with IntStream.range and mapToObj at the end. Are you sure your compiler complains or is it just your IDE? I just copied your code in IntelliJ IDEA 14 and it does not show any warnings or errors or any sort.Lenni
@EdwinDalorzo Have you tried List<A> = my code? It cannot infer that the result should be List<A>. About IntStream.range, the problem is that there should be a flatMapToObj or something. Can you try with mapToObj only and show me the code?Lentissimo
@Ali I definitely did and it worked like a charm. I am using Java 8 Build 20 and IntelliJ 14.Lenni
@EdwinDalorzo yes, you're right. It looks like IntelliJ can infer it, but Eclipse can't. How about IntStream.range, could you get that running?Lentissimo
@Ali Yes, I could make it work too. It is not a matter of type inference. The Java compiler does pretty well, but many IDEs are full of bugs in this area. Intellij 14 when it was just released had a lot of problems in this area and with every new release they have been improving a lot. Developers have been working on their own IDE type inference algorithms, I suppose, trying to emulate, as best as they can, what the compiler does. But chances are that there are still bugs here and there. With time, this will get better, but you should ultimate trust your compiler, not the IDE.Lenni
@EdwinDalorzo can you please post your version with range? I'm curious.Lentissimo
I
6

One way of using range is to perform a boxing conversion right afterwards:

List<A> list=IntStream.range(0, r_i).boxed()
  .flatMap(i -> IntStream.range(0, r_j).boxed()
    .flatMap(j -> IntStream.range(0, r_k)
      .mapToObj(k -> new A(i, j, k)))).collect(Collectors.toList());

It’s not the most beautiful code but IntStream.range(0, max).boxed() is still better than Stream.iterate(0, n -> n+1).limit(max)


One alternative is to use a real flattening operation rather than nested operations:

List<A> list=IntStream.range(0, r_i).boxed()
  .flatMap(i  -> IntStream.range(0, r_j).mapToObj(j -> new int[]{i,j}))
  .flatMap(ij -> IntStream.range(0, r_k).mapToObj(k -> new A(ij[0], ij[1], k)))
  .collect(Collectors.toList());

The main drawback I see is that it suffers from the absence of an IntPair or Tuple<int,int> type. So it uses an array as a work-around.

Isodynamic answered 6/1, 2015 at 16:54 Comment(0)
R
1

if it's ok to have mutable variable, you could get the List of class A like this....

List<A> newCollect = new ArrayList<>();
IntStream.range(0, r_i).forEach(
    i -> IntStream.range(0, r_j).forEach(
        j -> IntStream.range(0, r_k).forEach(
            k -> newCollect.add(new A(i, j, k))
        )
    )
);

or you can make List of List of List of A, then flatMap twice like this...

List<A> newCollect2 = IntStream.range(0, r_i).mapToObj(
    i -> IntStream.range(0, r_j).mapToObj(
        j -> IntStream.range(0, r_k).mapToObj(
            k -> new A(i, j, k)
        ).collect(Collectors.toList())
    ).collect(Collectors.toList())
)
.flatMap(a -> a.stream())
.flatMap(a -> a.stream())
.collect(Collectors.toList());
Royster answered 6/1, 2015 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.