Here is my code:
public static String removeAdjDuplicates(String s) {
if(s == "" || s == null || s.isEmpty())
return s;
if(s.length() < 2)
return s;
if(s.charAt(0) != s.charAt(1))
s = s.charAt(0) + removeAdjDuplicates(s.substring(1));
if(s.charAt(0) == s.charAt(1)) //line 37
return removeAdjDuplicates(s.substring(2));
return s;
}
With the input string "ull", I get the following error:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 1
at java.lang.String.charAt(String.java:658)
at GFG.removeAdjDuplicates(File.java:37)
at GFG.main(File.java:16)
I read and tried answers given to similar questions, but I'm not sure what is wrong.
Judging from the exception that you get, removeAdjDuplicates returns an empty string, invalidating all indexes past zero.
Although your code performs length checking at the top, it also performs this assignment when the two initial characters are different:
s = s.charAt(0) + removeAdjDuplicates(s.substring(1));
This means that s can become a one-character string if removeAdjDuplicates returns an empty string.
As you Try to pass this string "ull" to the method the last letter in the String should be the letter "u" because you use this
if(s.charAt(0) != s.charAt(1))
s = s.charAt(0) + removeAdjDuplicates(s.substring(1));
as you dont return the String back like the other conditions in the method it will continue to the next condition at line 37
and u have only one letter while the condition checking the first and the second characters ... there is no second letter so you get this error .. so the solution is to return s like this
if(s.charAt(0) != s.charAt(1)){
s = s.charAt(0) + removeAdjDuplicates(s.substring(1));
return s;
}
I think the source of the error is sufficiently explained by #dasblinkenlight's answer.
Although not clearly stated in the question, it looks like you're trying to remove adjacent duplicate letters recursively (one of your comments mentions that you would expect output s for input geegs).
Here's an alternative way to do it:
while(!s.equals(s = s.replaceAll("(.)\\1", "")));
It uses a regular expression to match and remove duplicate characters, and the while loop keeps executing this until the string is no longer being modified by the operation.
You should simplify your code:
public static String removeAdjDuplicates(String s) {
if (s == null || s.length() < 2)
return s;
if (s.charAt(0) != s.charAt(1))
return s.charAt(0) + removeAdjDuplicates(s.substring(1));
return removeAdjDuplicates(s.substring(2));
}
Changes
The first two if statements do the same thing (return s;) and can be combined into one. Some of the conditions are redundant and can be eliminated.
The third if statement should immediately return instead of continuing into the fourth if statement (or you can instead change the fourth if statement into an else), because removedAdjDuplicates can return an empty String making s a length-one String when the fourth if is expecting at least a length-two String.
The fourth if can be eliminated because if (s.charAt(0) != s.charAt(1)) failed in the third if, then the only alternative is that (s.charAt(0) == s.charAt(1)), so the check for that isn't necessary.
Related
I'm learning Java and while completing exercises I stumbled upon an issue in CodingBats sameStarChar program.
I know this is a simple exercise but the logic behind the different outcome is really bugging me.
When I write :
public boolean sameStarChar(String str) {
for (int i = 1; i < str.length() - 1; i++) {
if (str.charAt(i) == '*') {
if (str.charAt(i - 1) != str.charAt(i + 1))
return false;
}
}
return true;
}
All results are OK.
But when I change the code and invert the condition in the if block and return false as default return value, the code does not work anymore and some test fail:
public boolean sameStarChar(String str) {
for (int i = 1; i < str.length() - 1; i++) {
if (str.charAt(i) == '*') {
if (str.charAt(i - 1) == str.charAt(i + 1))
return true;
}
}
return false;
}
Can you please tell me why are the outcomes different? I can`t seem to find an exact explanation for this in any book.
Pay close attention to what the code is doing, in English:
Looping across all characters of a string, starting at 1 and going until 1 before its end
If the character at a given position i is *:
If the characters a position before and a position after are equal:
Return false.
Return true. Assume other scenario has its case exhausted.
The reason you get completely different results is because you completely flip the logic of the program. Here's your code, in English again:
Looping across all characters of a string, starting at 1 and going until 1 before its end
If the character at a given position i is *:
If the characters a position before and a position after are not equal:
Return true.
Return false. Assume other scenario has its case exhausted.
You haven't made false the default return option; you've inverted the entire program's logic. Consider the empty string, which is a valid test case. Your code said that this is invalid, when there's no asterisk to be had in the string (which would be a strange false positive).
The 1st case works because it returns true only if the * is preceded and followed by the same character or if the string doesn't contain a * at all.
The 2nd case doesn't work because it returns true if it contains at-least one * and first instance of * is preceded and followed by the same characters regardless of next instances of *. So if a blank string is passed it should return true but it instead returns false because it doesn't contain *. If another string *xa*a*b is passed the second program will return true because the instance of * follows the convention. The 2nd Program will return true right away, ignoring all the * after the it's first appearance.
I am getting a error when using this substring(). I am trying to get initials of first, middle, and last and then convert to upper case.
public String initials() {
String initials = first.substring(0, 1) + middle.substring(0, 1)
+ last.substring(0, 1);
return initials.toUpperCase();
}
That is the code and this is the output it is giving me..
Exception in thread "main" java.lang.NullPointerException
null, null null at name.Name.initials(Name.java:75)
at name.NameDriver.main(NameDriver.java:30)
Java Result: 1
Line 75 is
String initials = first.substring(0, 1) + middle.substring(0, 1)
Line 30 in NameDriver is
System.out.print(name1.initials());
This error is unrelated to the substring method and appears to be because first, middle, and last are null and this method can only be performed on a String.
The easiest way to stop this error occuring would be to add something like this at the start of your initials method:
if(first == null || middle == null || last == null) return "Not all names have been entered";
However, you may also want to consider that not everyone has a middle name.
Like #Andy just replied, just guard against any of these 3 being null.
Maybe just create and auxiliary function like:
private static String firstLetter(String name) {
if (name == null || name.isEmpty()) {
return "";
}
return name.substring(0, 1);
}
Then you can just change your code to:
public String initials() {
String initials = firstLetter(first) + firstLetter(middle) + firstLetter(last);
return initials.toUpperCase();
}
public static int getIndexOf(char ch, String str) {
if (str == null || str.equals("")) {
return 0;
//base case
}else{
char first = str.charAt(0);
if (ch != first) {
return -1;
//returns -1 when the character cannot be found within the string
}else{
int rest = str.length() - 1;
if (str.charAt(rest) == ch) {
return rest;
}
return lastIndexOf(ch, str.substring(0, rest));
//recursive case
}
}
}
This my method which returns the index of the input character of the input string. However, when I run it in the interaction plane, it returns wrong number. For example, when I enter 'a' and "peach", it is supposed to return 2, but it returns -1. This method should return -1 only when the character cannot be found in the string. Can anyone tell me how to deal with it?
Thank you!
Well why don't you step through the logic and see what happens.
getIndexOf('a', "peach")
Method goes in, string isn't null or empty so it falls through to the next line of code.
char first = str.charAt(0); // this is 'p'
if (ch != first) { // is 'p' equal to 'a'? no. so return -1
return -1;
And then the rest of your logic will never execute. Can you see how to fix this problem?
your following portion of code means that it will check for the first character of the string if it is matching otherwise it will return -1.
char first = str.charAt(0);
if (ch != first) {
return -1;
this says that if character at 0th index doesn't match then send -1 so as 'p' in "peach" isn't matching with 'a' so it is return -1.
did you get it?
The output's not wrong, the implementation is!
Think in words first. If the desired character is the first character in the string, then the result is zero. Otherwise it's (1 + the index in the string that remains after cutting off the first character). Now code the words:
return (str.charAt(0) == ch) ? 0 : 1 + getIndexOf(ch, str.substring(1));
This doesn't yet handle the case where the character is not in the string at all. Here the charAt(0) call will eventually throw IndexOutOfBoundsException because str doesn't have one!
The cleanest way to handle this case is to catch the exception. So you's have two functions: mustGetIndexOf, which is the recursive relation above, and getIndexOf, which calls the one above inside a try {} catch() {}, returning -1 in that case.
Of course if you don't want to allow the exception, you can test the recursive call's result with if for the special case of -1. The code is uglier, but it will work. Whenever a -1 is seen, return -1 again. This propagates the -1 all the way back to the caller. The exception "unwinds" the recursive calls on the stack in a similar manner, just with one chop instead of the gradual call-by-call way your if statements will do it.
I won't give you full code so you can continue to learn.
I want to reverse a sentence recursively and below is my following code. I wanted to know what other bases cases shud i take care of. And for the base case if string is null, how should that be handled?
public String reverse(String s) {
int n = s.indexOf(' ');
if(n == -1)
return s;
return reverse(s.substring(n+1))+" "+s.substring(0,n);
}
The reverse of null is null, so that's easy:
if(s == null) return null;
Because your method has the potential to return null, then, I would also do some null checking before referencing the value in your return statement and trying to append to it. so, something like...
String reversed = reverse(s.substring(n+1));
if(reversed != null) return reverse + " " + s.substring(0,n);
else return s;
Everything else looks fine. You shouldn't need any other base cases. Of course, this will reverse the sentence exactly as-is, including punctuation and case information. If you want to do this kind of thing, more strenuous processing will be required.
To ensure appropriate upper- and lower-case structure, I'd probably do something like this in your normal base case:
if(n == -1) {
s = s.toLowerCase();
String firstLetter = new String(s.charAt(0));
s = s.replaceFirst(firstLetter, firstLetter.toUpperCase());
return s;
}
Punctuation gets a little more complicated, especially if you have more than just an ending period, exclamation point, or question mark.
in your case, if the string is null, you can return an empty string (""). Returning a null will require you to handle the null in the calling functions and if you miss a case, you might encounter a NullPointerException
Basically what I'm trying to do is take a String, and replace each letter in the alphabet inside, but preserving any spaces and not converting them to a "null" string, which is the main reason I am opening this question.
If I use the function below and pass the string "a b", instead of getting "ALPHA BETA" I get "ALPHAnullBETA".
I've tried all possible ways of checking if the individual char that is currently iterated through is a space, but nothing seems to work. All these scenarios give false as if it's a regular character.
public String charConvert(String s) {
Map<String, String> t = new HashMap<String, String>(); // Associative array
t.put("a", "ALPHA");
t.put("b", "BETA");
t.put("c", "GAMA");
// So on...
StringBuffer sb = new StringBuffer(0);
s = s.toLowerCase(); // This is my full string
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
String st = String.valueOf(c);
if (st.compareTo(" ") == 1) {
// This is the problematic condition
// The script should just append a space in this case, but nothing seems to invoke this scenario
} else {
sb.append(st);
}
}
s = sb.toString();
return s;
}
compareTo() will return 0 if the strings are equal. It returns a positive number of the first string is "greater than" the second.
But really there's no need to be comparing Strings. You can do something like this instead:
char c = s.charAt(i);
if(c == ' ') {
// do something
} else {
sb.append(c);
}
Or even better for your use case:
String st = s.substring(i,i+1);
if(t.contains(st)) {
sb.append(t.get(st));
} else {
sb.append(st);
}
To get even cleaner code, your Map should from Character to String instead of <String,String>.
String.compareTo() returns 0 if the strings are equal, not 1. Read about it here
Note that for this case you don't need to convert the char to a string, you could do
if(c == ' ')
use
Character.isWhitespace(c)
that solves the issue. Best practice.
First, of all, what is s in this example? It's hard to follow the code. Then, your compareTo seems off:
if (st.compareTo(" ") == 1)
Should be
if (st.compareTo(" ") == 0)
since 0 means "equal" (read up on compareTo)
From the compareTo documentation: The result is a negative integer if this String object lexicographically precedes the argument string. The result is a positive integer if this String object lexicographically follows the argument string. The result is zero if the strings are equal;
You have the wrong condition in if (st.compareTo(" ") == 1) {
The compareTo method of a String returns -1 if the source string precedes the test string, 0 for equality, and 1 if the source string follows. Your code checks for 1, and it should be checking for 0.