Home > Software design >  How to set a value to variable based on multiple conditions using Java Streams API?
How to set a value to variable based on multiple conditions using Java Streams API?

Time:03-05

I couldn't wrap my head around writing the below condition using Java Streams. Let's assume that I have a list of elements from the periodic table. I've to write a method that returns a String by checking whether the list has Silicon or Radium or Both. If it has only Silicon, method has to return Silicon. If it has only Radium, method has to return Radium. If it has both, method has to return Both. If none of them are available, method returns "" (default value).

Currently, the code that I've written is below.

String resolve(List<Element> elements) {
    AtomicReference<String> value = new AtomicReference<>("");
    elements.stream()
            .map(Element::getName)
            .forEach(name -> {
                if (name.equalsIgnoreCase("RADIUM")) {
                    if (value.get().equals("")) {
                        value.set("RADIUM");
                    } else {
                        value.set("BOTH");
                    }
                } else if (name.equalsIgnoreCase("SILICON")) {
                    if (value.get().equals("")) {
                        value.set("SILICON");
                    } else {
                        value.set("BOTH");
                    }
                }
            });
    return value.get();
}

I understand the code looks messier and looks more imperative than functional. But I don't know how to write it in a better manner using streams. I've also considered the possibility of going through the list couple of times to filter elements Silicon and Radium and finalizing based on that. But it doesn't seem efficient going through a list twice.

NOTE : I also understand that this could be written in an imperative manner rather than complicating with streams and atomic variables. I just want to know how to write the same logic using streams.

Please share your suggestions on better ways to achieve the same goal using Java Streams.

CodePudding user response:

Collect the strings to a unique set. Then check containment in constant time.

Set<String> names = elements.stream().map(Element::getName).map(String::toLowerCase).collect(toSet());
boolean hasSilicon = names.contains("silicon");
boolean hasRadium = names.contains("radium");
String result = ""; 
if (hasSilicon && hasRadium) {
  result = "BOTH";
} else if (hasSilicon) {
  result = "SILICON";
} else if (hasRadium) {
  result = "RADIUM";
}
return result;

CodePudding user response:

i have used predicate in filter to for radium and silicon and using the resulted set i am printing the result.


import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class Test {

    public static void main(String[] args) {
        List<Element> elementss = new ArrayList<>();
        Set<String> stringSet = elementss.stream().map(e -> e.getName())
            .filter(string -> (string.equals("Radium") || string.equals("Silicon")))
            .collect(Collectors.toSet());
        if(stringSet.size()==2){
            System.out.println("both");
        }else if(stringSet.size()==1){
            System.out.println(stringSet);
        }else{
            System.out.println(" ");
        }
    }
    
}

CodePudding user response:

You could save a few lines if you use regex, but I doubt if it is better than the other answers:

String resolve(List<Element> elements) {
    String result = elements.stream()
                            .map(Element::getName)
                            .map(String::toUpperCase)
                            .filter(str -> str.matches("RADIUM|SILICON"))
                            .sorted()
                            .collect(Collectors.joining());

    return result.matches("RADIUMSILICON") ? "BOTH" : result;
}

CodePudding user response:

It could be done with Stream IPA in a single statement and without multiline lambdas, nested conditions and impure function that changes the outside the lambda.

My approach is to introduce an enum which elements correspond to all possible outcomes with its constants EMPTY, SILICON, RADIUM, BOTH.

All the return values apart from empty string can be obtained by invoking the method name() derived from the java.lang.Enum. And only to caver the case with empty string, I've added getName() method.

Note that since Java 16 enums can be declared locally inside a method.

It could be implemented like this:

public static String resolve(List<Element> elements) {
    return elements.stream()
            .map(Element::getName)
            .map(String::toUpperCase)
            .filter(str -> str.equals("SILICON") || str.equals("RADIUM"))
            .map(Elements::valueOf)
            .reduce((result, next) -> result == Elements.BOTH || result != next ? Elements.BOTH : next)
            .map(Elements::getName)
            .orElse("");
}

enum

enum Elements {EMPTY, SILICON, RADIUM, BOTH;
    String getName() {
        return this == EMPTY ? "" : name(); // note name() declared in the java.lang.Enum as final and can't be overridden
    }
}

main

public static void main(String[] args) {
    System.out.println(resolve(List.of(new Element("Silicon"), new Element("Lithium"))));
    System.out.println(resolve(List.of(new Element("Silicon"), new Element("RADIUM"))));
    System.out.println(resolve(List.of(new Element("Ferrum"), new Element("Oxygen"), new Element("Aurum")))
                .isEmpty()   " - no target elements"); // output is an empty string
}

output

SILICON
BOTH
true - no target elements

Note:

  • Although with streams you can produce the result in O(n) time iterative approach might better for this task. Think about it this way: if you have a list of 10.000 elements in the list and it starts with "SILICON" and "RADIUM". You could easily break the loop and return "BOTH".
  • Stateful operations in the streams has to be avoided according to the documentation, also to understand why javadoc warns against stateful streams you might take a look at this question. If you want to play around with AtomicReference it's totally fine, just keep in mind that this approach is not considered to be good practice.
  • Related