Home > Software design >  Stream find first multiple option
Stream find first multiple option

Time:11-08

I have a stream and I would like to extract one value based on the condition :

  • If any match condition A find first that match A
  • Else if any match condition B find first that match B
  • Else if any match condition C find first that match C
  • Else null

For example :

Stream<String> stream0 = Stream.of("a", "b", "c", "d");
Stream<String> stream1 = Stream.of("b", "c", "d", "a");
Stream<String> stream2 = Stream.of("b", "c", "d", "e");
Stream<String> stream3 = Stream.of("d", "e", "f", "g");

findBestValue(stream0); //should return "a"
findBestValue(stream1); //should return "a"
findBestValue(stream2); //should return "b"
findBestValue(stream3); //should return null

I tried the following, but that return a java.lang.IllegalStateException: stream has already been operated upon or closed

private static String findBestValue(Stream<String> stream) {
    return stream.filter(str -> str.equals("a"))
            .findFirst()
            .orElse(stream.filter(str -> str.equals("b"))
                    .findFirst()
                    .orElse(stream.filter(str -> str.equals("c"))
                            .findFirst()
                            .orElse(null))
            );
}

Any idea how I could archive that ?

Note : a, b, c are just for the example I can't use .sort()

CodePudding user response:

Once the stream is consumed - it's done, and you can't use it anymore. Otherwise, you would get an IllegalStateException.

Because Stream is a mean of iteration of the source of data, not a container of data.

Here's a quote from the API documentation:

Streams differ from collections in several ways:

  • No storage. A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.

You can store the incoming stream data into a collection, and then interact with it. As a collection we can use a LinkedHashMap (for the example with strings a set, would be enough, but if we generify the code the map would be useful to deal with complex objects that are contrary to string not necessarily identical even if they are equal).

That's how it might be implemented without using hard-coded conditions:

private static <T> T findBestValue(Stream<T> stream,
                                   T key1, T key2, T key3) {
    
    Map<T, T> map = stream.collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (l, r) -> l,         // preserve the first value - precaution against duplicates
        LinkedHashMap::new
    ));
    
    return Stream.of(map.get(key1), map.get(key2), map.get(key3))
        .filter(Objects::nonNull)
        .findFirst()
        .orElse(null); // NOTE that it's NOT the most recommended option (we can return Optional itself or use methods like orElseThrow())
}

And we can make this method expect a varargs of keys:

private static <T> T findBestValue(Stream<T> stream,
                                   T... keys) {
    
    Map<T, T> map = stream.collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (l, r) -> l,
        LinkedHashMap::new
    ));
    
    return Arrays.stream(keys)
        .map(map::get)
        .filter(Objects::nonNull)
        .findFirst()
        .orElse(null);
}

main()

public static void main(String[] args) {
    Stream<String> stream1 = Stream.of("a", "b", "c", "d");
    Stream<String> stream2 = Stream.of("b", "c", "d", "e");
    Stream<String> stream3 = Stream.of("d", "e", "f", "g");

    System.out.println(findBestValue(stream1, "a", "b", "c")); //should return "a"
    System.out.println(findBestValue(stream2, "a", "b", "c")); //should return "b"
    System.out.println(findBestValue(stream3, "a", "b", "c")); //should return null
}

Output:

a
b
null

CodePudding user response:

Good explanation of the error here: https://www.baeldung.com/java-stream-operated-upon-or-closed-exception

Basically, you can only run a stream once.

If you really want to do it this way, try:

    private static String findBestValue(List<String> list) {
        return list.stream().filter(str -> str.equals("a"))
                .findFirst()
                .orElse(list.stream().filter(str -> str.equals("b"))
                        .findFirst()
                        .orElse(list.stream().filter(str -> str.equals("c"))
                                .findFirst()
                                .orElse(null)));
    }

CodePudding user response:

@Alexander Ivanchenko's solution can also be written as

private static <T> T findBestValue(Stream<T> stream, T... keys) {
    Set<T> set = stream.collect(Collectors.toSet());
    return Stream.of(keys)
        .filter(set::contains)
        .findFirst()
        .orElse(null);
}

public static void main(String[] args) {
    Stream<String> stream0 = Stream.of("a", "b", "c", "d");
    Stream<String> stream1 = Stream.of("b", "c", "d", "a");
    Stream<String> stream2 = Stream.of("b", "c", "d", "e");
    Stream<String> stream3 = Stream.of("d", "e", "f", "g");

    System.out.println(findBestValue(stream0, "a", "b", "c")); //should return "a"
    System.out.println(findBestValue(stream1, "a", "b", "c")); //should return "a"
    System.out.println(findBestValue(stream2, "a", "b", "c")); //should return "b"
    System.out.println(findBestValue(stream3, "a", "b", "c")); //should return null
}

output:

a
a
b
null
  • Related