Simplifying/optimizing massive if...else if...else statement(s) - java

Okay so essentially, I have some code that uses the contains() method to detect the presence of specific characters in two strings. For extra context, this question is a good resource as to what kind of problem I'm having (and the third solution is also something I've looked into for this). Regardless, here is some of my code:
// code up here basically just concatenates different
// characters to Strings: stringX and stringY
if (stringX.contains("!\"#")) {
} else if (stringX.contains("$%&")) {
} else if (stringX.contains("\'()")) {
} else if (stringX.contains("!$\'")) {
} else if (stringX.contains("\"%(")) {
// literally 70+ more else-if statements
}
if (stringY.contains("!\"#")) {
} else if (stringY.contains("$%&")) {
} else if (stringY.contains("\'()")) {
} else if (stringY.contains("!$\'")) {
} else if (stringY.contains("\"%(")) {
// literally 70+ more else-if statements, all of which are
// exactly the same as those working with stringX
}
I'm still pretty new to Java programming, so I'm not sure how I should go about this. Maybe it is a non-issue? Also, if I can remedy this without using RegEx, that would be preferable; I am not very knowledgeable in it at this point it time. But if the only rational solution would be to utilize it, I will obviously do so.
Edit: The code within all of these else-if statements will not be very different from each other at all; basically just a System.out.println() with some information about what characters stringX/stringY contains.

Writing the same code more than once should immediately set off alarm bells in your head to move that code into a function so it can be reused.
As for simplifying the expression, the best approach is probably storing the patterns you're looking for as an array and iterating over the array with your condition.
private static final String[] patterns = new String[] {"!\"#", "$%&", "\'()", "!$\'", "\"%(", ...};
private static void findPatterns(String input) {
for (String pattern : patterns) {
if (input.contains(pattern) {
System.out.println("Found pattern: " + pattern);
}
}
}
// Elsewhere...
findPatterns(stringX);
findPatterns(stringY);
This pattern is especially common in functional and functional-style languages. Java 8 streams are a good example, so you could equivalently do
List<String> patterns = Arrays.asList("!\"#", "$%&", "\'()", "!$\'", "\"%(", ...);
patterns.stream()
.filter(pattern -> stringX.contains(pattern))
.forEach(pattern -> System.out.println("Found pattern: " + pattern));

can simply by make a list of your case. then using java 8 stream filter
List<String> pattems = Arrays.asList("!\"#", "$%&", ...);
Optional<String> matched = pattems.stream().filter(p -> stringX.contains(p));
if(matched.isPresent()) {
System.console().printf(matched.get())
}
java stream could make your peformance slower but not too much

Related

How to set a value to variable based on multiple conditions using Java Streams API?

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.
It could be done with Stream IPA in a single statement and without multiline lambdas, nested conditions and impure function that changes the state 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.
The logic of the stream pipeline is the following:
stream elements turns into a stream of string;
gets filtered and transformed into a stream of enum constants;
reduction is done on the enum members;
optional of enum turs into an optional of string.
Implementation can look 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 be 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.
I guess if I had implemented such a method with streams, the overall logic would be the same as above, but without utilizing an enum. Since only a single object is needed it's a reduction, so I'll apply reduce() on a stream of strings, extract the reduction logic with all the conditions to a separate method. Normally, lambdas have to be well-readable one-liners.
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;
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(" ");
}
}
}
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;
}

Replacing special characters from a string

Just would like to know if there is a more elegant and maintainable approach for this:
private String replaceSpecialChars(String fileName) {
if (fileName.length() < 1) return null;
if (fileName.contains("Ü")) {
fileName = fileName.replace("Ü", "Ue");
}
if (fileName.contains("Ä")) {
fileName = fileName.replace("Ä", "Ae");
}
if (fileName.contains("Ö")) {
fileName = fileName.replace("Ö", "Oe");
}
if (fileName.contains("ü")) {
fileName = fileName.replace("ü", "ue");
}
...
return fileName;
}
I'm restricted to Java 6.
Before you go any further on this, note that what you're doing is effectively impossible. For example, the 'ascii-fication' of 'Ö' in swedish is 'O' and not 'Oe'. There is no way to know if a word is swedish or german; after all, swedes sometimes move to germany, for example. If you open a german phonebook and you see a Mrs. Sjögren, and you asciify that to Sjoegren, you messed it up.
If you want to run 'case and asciification insensitive comparisons', well, first you have to answer a few questions. Is muller equal to mueller equal to müller? That rabbit hole goes quite deep.
The general solution is trigrams or other generalized text search tools such as provided by postgres. Alternatively, opt out of this mechanism and store this stuff in unicode, and be clear that to find Ms. Sjögren, you're going to have search for "Sjögren" for the same reason that to find Mr. Johnson, you're not going to if you try to search for Jahnson.
Note that most filesystems allow unicode filenames; there is no need to try to replace a Ü.
This also goes some way as to explain why there are no ready libraries available for this seemingly common job; the job is, in fact, impossible.
You can simplify this code by using a Map<String, String> with replacements if you must. I advise against it for the above reasons. Or, just.. keep it as is, but ditch the contains. This code is needlessly slow and lengthy.
There is no difference between:
if (fileName.contains("x")) fileName = fileName.replace("x", "y");
and just fileName = fileName.replace("x", "y"); except that the former is strictly slower (replace does not make a new string and returns itself, if you ask it to replace a string that it does not contain. The former will search twice, the latter only once, and either one will make no new strings unless actual string replacing needs to be done.
You can then chain it:
if (fileName.isEmpty()) return null;
return fileName
.replace("Ü", "Ue")
.replace("Ä", "Ae")
...
;
But, as I said, you probably don't want to do that, unless you want an aggravated person on the line at some point in the future complaining that you bungled up the asciification of their surname.
You can remove unnecessary if statements an use a chain of String.replace methods. Your code might look something like this:
private static String replaceSpecialChars(String fileName) {
if (fileName == null)
return null;
else
return fileName
.replace("Ü", "Ue")
.replace("Ä", "Ae")
.replace("Ö", "Oe")
.replace("ü", "ue");
}
public static void main(String[] args) {
System.out.println(replaceSpecialChars("ABc")); // ABc
System.out.println(replaceSpecialChars("ÜÄÖü")); // UeAeOeue
System.out.println(replaceSpecialChars("").length()); // 0
System.out.println(replaceSpecialChars(null)); // null
}

Java Error/Exception handling with returning value

So my friend and I are programming Blackjack in Java, and we wanted to test our input fields for the correct input(e.g only number input). So we sat at his PC and he wrote this solution:
public static boolean testeTextFieldInt(JTextField textField, int geld) {
if (!textField.getText().isEmpty()) {
try {
if(Integer.parseInt(textField.getText())>0 && Integer.parseInt(textField.getText())<geld ) {
return true;
}
} catch (NumberFormatException e) {
return false;
}
}
return false;
}
now I disagree with this solution, because your code shouldn't depend on an error, or am I getting this wrong? so i sat down and wrote this:
public static boolean checkInput(JTextField textField, int spielerGeld, String eingabe) {
boolean matched = false;
switch (eingabe) {
case "num":
if (!textField.getText().isEmpty() && textField.getText().matches("^[0-9]*$")) {
int geldinput = Integer.parseInt(textField.getText());
if (geldinput > 0 && geldinput < spielerGeld) {
matched = true;
}
}
break;
case "string":
if (!textField.getText().isEmpty() && textField.getText().matches("^[a-zA-Z]*$")) {
matched = true;
}
break;
default:
break;
}
return matched;
}
Keep in mind, we yet dont have any textfields we have to check, but I just implemented it to get a grasp of how you could do multiple checks within one method.
So now my question is, what code is "better"? and what could we/I do better?
Thanks in advance!
EDIT1:
So as some already have mentioned, you say my method is not build up after the Single responsibility principle.
But if split up into 'checkInputIsnumber' and checkInputIsString' would the first solution(my friend), still be the "better" one?
EDIT2:
Better is defined as in, the method should be of low cyclomatic complexity, easy readability and be easy to maintain in the long run.
The first approach is much better than the second one.
Single responsibility: You should avoid creating methods that do more than one thing.
Open–closed principle: Your 'validation' is not extensible. Try creating a Validator interface and then an implementation per validation type.
Switch statements increase cyclomatic complexity and make testing harder.
Also, don't use textField.getText() everywhere, it's quite possible that it will change between calls. Assign it to a local variable or even better use a String as your argument and not JText. As Fildor pointed out you correctly avoid using exceptions for flow control and it is indeed better to have a single return point. Having said that, for simple cases when you just parse/check and return, it is acceptable.
You should put every check in a single function. After a while your "all in one function" will be unreadable an unmaintainable. Also it easier to change the checks if they are in single functions. Using try/catch for control flow is no good idea. It is expensive at runtime. It is not a good style and most developers won't expect control flow in a catch block.Excpetions are for exceptional situations.

One line check if String contains bannedSubstrings

I have a String title and a List<String> bannedSubstrings. Now I want to perform a one line check if title is free of those bannedSubstrings.
My approach:
if(bannedSubstrings.stream().filter(bannedSubstring -> title.contains(bannedSubstring)).isEmpty()){
...
}
Unfortunately, there is no isEmpty() method for streams. So how would you solve the problem? Is there a one line solution?
Sounds like you want to read up on anyMatch:
if (bannedSubstrings.stream().anyMatch(title::contains)) {
// bad words!
}
Inversely, there's also noneMatch:
if (bannedSubstrings.stream().noneMatch(title::contains)) {
// no bad words :D
}
This isn't very efficient if title is a long string (but titles usually aren't supposed to be long, I suppose).
If you want an efficient solution and you have many bannedSubstrings, I guess, it would be faster to join them into single regexp like this:
Pattern badWords = Pattern.compile(bannedSubstrings.stream().map(Pattern::quote)
.collect(Collectors.joining("|")));
Then use it like this:
if (badWords.matcher(title).find()) {
...
}
This should build a prefix tree from your substrings, so scanning will be significantly faster. If performance is not the concern in your case, use other answers.
I suppose you are looking for something like this:
if(bannedSubstrings.stream().anyMatch(title::contains)){
}
The answer you've selected is pretty good, but for real performance you'd probably be better off pre-compiling the list of bad words into a regex.
public class BannedWordChecker {
public final Pattern bannedWords;
public BannedWordChecker(Collection<String> bannedWords) {
this.bannedWords =
Pattern.compile(
bannedWords.stream()
.map(Pattern::quote)
.collect(Collectors.joining("|")));
}
public boolean containsBannedWords(String string) {
return bannedWords.matcher(string).find();
}
}

Test multiple .equals() at once

I wanted to know if there was a way to shorten this if statement with the ".equals" so that I can test things in one line, instead of multiple if statements.
This is an excerpt my current long winded code. (This is what I want to shorten)
if (queryArray[1].equals("+")) {
System.out.println("Got +");
} else if (queryArray[1].equals("-")) {
System.out.println("Got -");
} else if (queryArray[1].equals("*")) {
System.out.println("Got *");
}
I tried doing this (Does not work) to reduce the number of lines needed.
if (queryArray[1].equals("+","-","*")) {
System.out.println("Got +");
}
And even (Does not work):
if (queryArray[1].equals("+" || "-" || "*")) {
System.out.println("Got +");
}
Also, I know about the or syntax "||" within if statements, however I'm looking to shorten it within the ".equals()" method.
Is there any way to shorten this code? Thank you.
Since you're only doing single-character comparisons, you can do a switch on queryArray[1].charAt(0).
switch (queryArray[1].charAt(0)) {
case '+':
// plus thing
break;
case '-':
// minus thing
break
// ... and so on
}
Or if you're using Java 7, you can switch directly on the string.
With Java 7, you can do a switch on strings:
switch(queryArray[1]) {
case "+":
case "*":
case "-":
System.out.println("Got " + queryArray[1]);
break;
default:
// do nothing
}
you can even do it in this way
List<String> list = Arrays.asList("+","-","*");
if(list.contains(queryArray[1]))
System.out.println("Got "+queryArray[1]);
First off your alternative syntax inside the .equals() isn't valid Java.
Unless you have way more than a few tests and each one of them has lots of cyclomatic complexity in each condition, there isn't any compelling reason to do what you are asking.
That said, you need to flip the problem on its head and do something like the following:
interface Handler { public void handle(); }
final Map<String, Handler> symbols = new HashMap<String, Handler>();
symbols.put("+", new Handler() {
public void handle() { System.out.println("Got +"); }
};
symbols.put("-", new Handler() {
public void handle() { System.out.println("Got -"); }
};
symbols.put("*", new Handler() {
public void handle() { System.out.println("Got *"); }
};
Then the logic tests are reduced to:
symbols.get(queryArray[1]).handle();
This won't be any faster than the individual if/elseif/else construct, but it does something like you are looking for to reduce the lines of code.
This is a common Object Oriented Design pattern, it is a variation on the Chain of Responsibility Pattern.
It is very useful when there are many alternatives in an if/elseif/else construct and the logic in each alternative is complicated.
It makes adding alternatives simple as implementing the interface and adding the alternative to the Map.
It also makes maintenance a very easy as well. Because it promotes Encapsulation of the rules and Cohesion of the logic. Something that is gets completely lost in very large if/elseif/else blocks.
You don't have to use Anonymous Inner Classes as in my example, they can be regular classes that are in their own files or regular Inner Classes.
Try this
Map<String,String> resultMap = new HashMap<String,String>();
resultMap.put("+","Got +");
resultMap.put("-","Got -");
resultMap.put("*","Got *");
System.out.println(resultMap.get(queryArray[1]));
The code block you provided is the most effecient and more readable. and if considered scalabilty and maintenance, it shouldn't be refactore if logic doesn't change.
if (queryArray[1].equals("+"))
{
System.out.println("Got +");
}
else if (queryArray[1].equals("-"))
{
System.out.println("Got -");
}
else if (queryArray[1].equals("*"))
{
System.out.println("Got *");
}
However Borealid has given switch-case construct but a little bit of logic change will initiate a lot of changes and probably bugs-crawling also.
Well, I'm too providing a solution on same lines, but it's also not better than the code you provided:
System.out.println(queryArray[1].equals("+")?"Got +"
:queryArray[1].equals("-")?"Got -"
:queryArray[1].equals("*")?"Got *"
:"");
if your problem is that method size is increasing, try creating a separate method which returns a string (to be printed), so the equals-comparison method can be moved to a separate block.
And, one more thing to say, the || and && operators should be used with boolean operands. and, before calling an API check it's javadoc: equals
On a single line ...
if (Arrays.asList("+", "-", "*").contains(queryArray[1])) {
System.out.println("BINGO!");
}
This works because asList has a varargs parameter.
However, this code involves creating and initializing a new String[], wrapping it in a new List and then iterating over the list. So don't do it if performance is likely to be a concern.
Something even more obscure:
char a = queryArray[1].charAt(0);
if ((a - '*') * (a - '+') * (a - '-') == 0) {
/* process here. */
}
Rather useless if you want to compare more than one character, though.
The shortest Java-solution I can think of is:
System.out.println (Arrays.asList ("+", "-", "*").contains ("-"));

Categories

Resources