get wrong output of a recursive method - java

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.

Related

What does it mean if something is equal to -1?

Can anyone explain to me what it means?
Example:
for(char ch : (a+b).toCharArray()){
if(a.indexOf(ch) == -1 || b.indexOf(ch) == -1){
...
}
}
I want to understand the meaning of -1.
a.indexOf(ch) == -1:
It means that the character ch is not found in string a
Extended explanation:
indexOf() : This method returns the index within this string of the first occurrence of the specified character or -1, if the character does not occur.
-1 if it never occurs.
learn more about the method here -
https://www.w3schools.com/java/ref_string_indexof.asp

"Find substring in char[]" getting unexpected results

Disclaimer: This is a bit of a homework question. I'm attempting to write a contains(java.lang.String subString) method , that returns an int value representing the index of the comparison string within the primary string, for a custom-made String class.
Some of the rules:
No collection classes
Only charAt() and toCharArray() are allowed from the java String class (but methods from other classes are allowed)
Assume length() returns the length of the primary string (which is exactly what it does)
My Code:
public int contains(java.lang.String subString) {
this.subString = subString;
char[] arrSubStr = this.subString.toCharArray();
//Create initial fail
int index = -1;
//Make sure comparison subString is the same length or shorter than the primary string
if(arrSubStr.length > length()) {
return index;
}
//Steps to perform if initial conditions are met
else {
//Compare first character of subString to each character in primary string
for(int i = 0; i < length(); i++) {
//When a match is found...
if(arrSubStr[0] == this.content[i]) {
//...make sure that the subString is not longer than the remaining length of the primary string
if(arrSubStr.length > length() - i) {
return index;
}
//Proceed matching remainder of subString
else {
//Record the index of the beginning of the subString contained in primary string
index = i;
//Starting with second character of subString...
for(int j = 1; j < arrSubStr.length;) {
//...compare with subsequent chars of primary string,
//and if a failure of match is found, reset index to failure (-1)
if(arrSubStr[j] != this.content[j+i]) {
index = -1;
return index;
}
//If we get here, it means whole subString match found
//Return the index (=i) we set earlier
else {
return index;
}
}
}
}
}
}
return index;
}
Results from testing:
Primary string: asdfg
Comparison string: donkey
Result: -1 [PASS]
Primary string: asdfg
Comparison string: asdfg
Result: 0 [PASS]
Primary string: asdfg
Comparison string: g
Result: 4 [PASS]
Primary string: asasasf
Comparison string: asd
Result: 0 [FAIL] (should be -1)
Primary string: asasasf
Comparison string: asf
Result: 0 [FAIL] (should be 4)
The comments reflect how the code is intended to work. However its clear that when it reaches the second for loop, the logic is breaking down somehow to give the results above. But I can't see the problem. Could I get a second set of eyes on this?
//If we get here, it means whole subString match found
//Return the index (=i) we set earlier
else {
return index;
}
This assumption is not correct unfortunately. If you get there, it means that the second character of both substrings are identical since the if-else statement will only get executed once and both ends contains a return.
The way to solve this is probably easy now that I've diagnosed the problem but I want to go a bit further with this. The way we try to write code on a daily basis is a way in which the code we use can be maintainable, reusable and testable.
This means basically that the function we have here could be easily sliced up in different little functions invoked one after the other for which we could write unit tests and receive a quick feedback on whether a set of logical statements fit or not.
With suggestions from Jai and azurefrog in the comments, I was able to solve the issues by re-writing the logic to the following (somewhat abridged):
if(arrSubStr.length > length()) {
return index;
}
//Steps to perform if initial conditions are met
else {
//Compare first character of subString to each character in primary string
for(int i = 0; i < length(); i++) {
//When a match is found...
if(arrSubStr[0] == this.content[i]) {
//...make sure that the subString is not longer than the remaining length of the primary string
if(arrSubStr.length <= length() - i) {
//Record the index of the beginning of the subString contained in primary string
index = i;
//Starting with second character of subString...
for(int j = 1; j < arrSubStr.length; j++) {
//...compare with subsequent chars of primary string,
//and if a failure of match is found, reset index to failure (-1)
if(arrSubStr[j] != this.content[j+i]) {
index = -1;
break;
}
}
}
}
}
}
return index;
Essentially, I removed all of the return statements from within the loops. Simply setting the index value appropriately and making use of the final (outside) return statement was, in hindsight, the correct way to approach the problem. I then also added a break; to the inner for loop to make sure that a failure to match would continue the loop ticking through. I'm sure there's still unnecessary code in there, but while its still passing the requisite tests, I'm encouraged to leave it the hell alone. :)
I'm still a novice at Java, so I hope this explanation made sense.

StringIndexOutOfBounds when removing adjacent duplicate letters

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.

Why does the default Boolean return value influence my tests outcome

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.

Java: Efficient way to determine if a String meets several criteria?

I would like to find an efficient way (not scanning the String 10,000 times, or creating lots of intermediary Strings for holding temporary results, or string bashing, etc.) to write a method that accepts a String and determine if it meets the following criteria:
It is at least 2 characters in length
The first character is uppercased
The remaining substring after the first character contains at least 1 lowercased character
Here's my attempt so far:
private boolean isInProperForm(final String token) {
if(token.length() < 2)
return false;
char firstChar = token.charAt(0);
String restOfToken = token.substring(1);
String firstCharAsString = firstChar + "";
String firstCharStrToUpper = firstCharAsString.toUpperCase();
// TODO: Giving up because this already seems way too complicated/inefficient.
// Ignore the '&& true' clause - left it there as a placeholder so it wouldn't give a compile error.
if(firstCharStrToUpper.equals(firstCharAsString) && true)
return true;
// Presume false if we get here.
return false;
}
But as you can see I already have 1 char and 3 temp strings, and something just doesn't feel right. There's got to be a better way to write this. It's important because this method is going to get called thousands and thousands of times (for each tokenized word in a text document). So it really really needs to be efficient.
Thanks in advance!
This function should cover it. Each char is examined only once and no objects are created.
public static boolean validate(String token) {
if (token == null || token.length() < 2) return false;
if (!Character.isUpperCase(token.charAt(0)) return false;
for (int i = 1; i < token.length(); i++)
if (Character.isLowerCase(token.charAt(i)) return true;
return false;
The first criteria is simply the length - this data is cached in the string object and is not requiring traversing the string.
You can use Character.isUpperCase() to determine if the first char is upper case. No need as well to traverse the string.
The last criteria requires a single traversal on the string- and stop when you first find a lower case character.
P.S. An alternative for the 2+3 criteria combined is to use a regex (not more efficient - but more elegant):
return token.matches("[A-Z].*[a-z].*");
The regex is checking if the string starts with an upper case letter, and then followed by any sequence which contains at least one lower case character.
It is at least 2 characters in length
The first character is
uppercased
The remaining substring after the first character contains
at least 1 lowercased character
Code:
private boolean isInProperForm(final String token) {
if(token.length() < 2) return false;
if(!Character.isUpperCase(token.charAt(0)) return false;
for(int i = 1; i < token.length(); i++) {
if(Character.isLowerCase(token.charAt(i)) {
return true; // our last criteria, so we are free
// to return on a met condition
}
}
return false; // didn't meet the last criteria, so we return false
}
If you added more criteria, you'd have to revise the last condition.
What about:
return token.matches("[A-Z].*[a-z].*");
This regular expression starts with an uppercase letter and has at least one following lowercase letter and therefore meets your requirements.
To find if the first character is uppercase:
Character.isUpperCase(token.charAt(0))
To check if there is at least one lowercase:
if(Pattern.compile("[a-z]").matcher(token).find()) {
//At least one lowercase
}
To check if first char is uppercase you can use:
Character.isUpperCase(s.charAt(0))
return token.matches("[A-Z].[a-z].");

Categories