I would like to return permutations of strings from all strings. When i use below code, i get java.lang.IllegalStateException: stream has already been operated upon or closed
. Could you please tell me what is wrong with this approach ?
public Stream<String> getMyPatterns(
Stream<String> s1,
Stream<String> s2,
Stream<String> s3) {
List<String> out = new ArrayList<String>();
s1.collect(Collectors.toList()).forEach(item1 -> {
s2.collect(Collectors.toList()).forEach(item2 -> {
s3.collect(Collectors.toList()).forEach(item3 -> {
out.add(item1 + "." + item2 + "." + item3);
});
});
});
return out.stream();
}
Sample test
Stream<String> patterns = getMyPatterns(
Stream.of("a1", "a2"),
Stream.of("b1", "b2"),
Stream.of("c1", "c2"));
As hinted by others, you are using the Streams s2
and s3
within forEach
multiple times.
On the other hand, collecting the result list before returning a Stream, is pointless. You shouldn’t use forEach
anyway, when there are other, more suitable operations.
You only need to collect the Stream you want to combine with the previous one. This is best accomplished by first solving it for a combination of two Streams:
public Stream<String> getMyPatterns(Stream<String> s1, Stream<String> s2) {
List<String> s2List = s2.collect(Collectors.toList());
return s1.flatMap(item1 -> s2List.stream().map(item2 -> item1+'.'+item2));
}
Then, you can easily use this method recursively to solve the task of combining three Streams:
public Stream<String> getMyPatterns(Stream<String> s1,Stream<String> s2,Stream<String> s3) {
return getMyPatterns(getMyPatterns(s1, s2), s3);
}
This collects only as little as necessary, i.e. it won’t collect the first Stream, so its consumption will only start when the returned Stream is consumed.
Stream<String> patterns = getMyPatterns(
Stream.of("a1", "a2").peek(s -> System.out.println("consuming "+s)),
Stream.of("b1", "b2"),
Stream.of("c1", "c2"));
System.out.println("start consuming result Stream");
System.out.println(patterns.collect(Collectors.joining(", ")));
start consuming result Stream
consuming a1
consuming a2
a1.b1.c1, a1.b1.c2, a1.b2.c1, a1.b2.c2, a2.b1.c1, a2.b1.c2, a2.b2.c1, a2.b2.c2
Stream can be consumed or operated only once
public static Stream<String> getMyPatterns(
Stream<String> s1,
Stream<String> s2,
Stream<String> s3) {
List<String> c1 = s1.collect(Collectors.toList());
List<String> c2 = s2.collect(Collectors.toList());
List<String> c3 = s3.collect(Collectors.toList());
BiFunction<String, Stream<String>, Stream<String>> func = (element, partialResultStream) -> partialResultStream.map(x -> x + "." + element);
return c1.stream().flatMap(x -> func.apply(x, c2.stream()))
.flatMap(x -> func.apply(x, c3.stream()));
}
Output
[c1.b1.a1, c2.b1.a1, c1.b2.a1, c2.b2.a1, c1.b1.a2, c2.b1.a2, c1.b2.a2, c2.b2.a2]
The stream should be consumed or operated one time, but in your solution you consume it multiple times in a loop, instead you can solve your issue like so :
public static Stream<String> getMyPatterns(Stream<String> s1, Stream<String> s2, Stream<String> s3) {
List<String> out = new ArrayList<String>();
List<String> c1 = s1.collect(Collectors.toList());
List<String> c2 = s2.collect(Collectors.toList());
List<String> c3 = s3.collect(Collectors.toList());
c1.forEach(item1 -> {
c2.forEach(item2 -> {
c3.forEach(item3 -> {
out.add(item1 + "." + item2 + "." + item3);
});
});
});
return out.stream();
}
You can achieve this using collections, and then transforming it to streams to avoid the reuse of them:
public static void main(String[] args) {
Stream<String> combine = combine(Arrays.asList("1", "2", "3"), Arrays.asList("4", "5", "6"), Arrays.asList("7", "8", "9"));
combine.forEach(System.out::println);
}
static Stream<String> combine(List<String> s1, List<String> s2, List<String> s3){
return s1.stream().flatMap(v1 ->
s2.stream().flatMap(v2 ->
s3.stream().flatMap(v3 -> Stream.of(v1 + "." + v2 + "." + v3 + ";"))
)
);
}