Recursion behaving in an unexpected manner with Java - java

I'm a student, and I've been working on the following challenge: find a substring (the needle) in a larger string (the haystack) without using the substring method, and using recursion. Recursion isn't my strong suit, but I have worked out the following:
public class Contains
{
public static void main(String[] args)
{
System.out.println(contains("Java programming", "ogr", false));
}
public static boolean contains(String haystack, String needle, boolean doesContain)
{
if(haystack.length() < needle.length())
{
return false;
}
else
{
for(int i = 0; i < needle.length(); i++)
{
if(haystack.charAt(i) != needle.charAt(i))
if((i + 1) == needle.length())
{
doesContain = false;
break;
}
else
break;
else
if((i + 1) == needle.length())
{
doesContain = true;
break;
}
else
continue;
}
char[] haystackChar = haystack.toCharArray();
char[] newCharArray = new char[(haystackChar.length - 1)];
for(int j = 1; j < haystackChar.length; j++)
{
newCharArray[j - 1] = haystackChar[j];
}
String newStr = new String(newCharArray);
if(doesContain == false)
contains(newStr, needle, doesContain);
}
return doesContain;
}
}
I realize this might not be the best or most elegant solution, but I am mostly just trying to get it to work. I've been running it in the Eclipse debugger, and everything is functioning as expected up until the call to if(doesContain == false) during the method call to contain where doesContain is set to true during the iteration of the for loop. The debugger is showing the value of doesContain to (correctly) be true, and it shows it skipping over the if statement, and exiting the else block. However, immediately after that, it jumps back up into the else block and only calls the recursive call to contain, instead of returning doesContain. Then, it continues to work recursively and subsequently fail and return false, because it's now searching through the rest of the string, where the "needle" is not located.
I know that StackOverflow is not a 'homework help' location per se, but I program for purposes other than school, and I'm quite perplexed as to why it's behaving this way. Does anyone know why it's doing this? Am I missing something here?

I took a look through your code and ran it in eclipse myself. A theory you will want to look into is how stacking works in recursion. Your program is finding true and then leaving the stack, but by that point it had reoccurred several times. It returned true, but then also went on to return all the false variables that were stored before it.
If you have any further questions please let me know.
EDIT
If you are really interested in getting into advanced recursion I highly recommend this video: Java Recursion
Hey, I didn't need to go that far to make it work. You can remove doesContain as a parameter and set it as a static instance variable and it worked for me.
public class Contains
{
private static boolean doesContain = false;
public static void main(String[] args)
{
System.out.println(contains("Java programming", "ogr"));
}
public static boolean contains(String haystack, String needle)
{
if(haystack.length() < needle.length())
{
return false;
}
else
{
for(int i = 0; i < needle.length(); i++)
{
if(haystack.charAt(i) != needle.charAt(i))
if((i + 1) == needle.length())
{
doesContain = false;
break;
}
else
break;
else
if((i + 1) == needle.length())
{
doesContain = true;
break;
}
else
continue;
}
char[] haystackChar = haystack.toCharArray();
char[] newCharArray = new char[(haystackChar.length - 1)];
for(int j = 1; j < haystackChar.length; j++)
{
newCharArray[j - 1] = haystackChar[j];
}
String newStr = new String(newCharArray);
if(doesContain == false)
contains(newStr, needle);
}
return doesContain;
}
}
What you had was very close, but by passing it as a parameter you were storing every time you went through another recursion. This way you only return your final value.

To find a needle in the haystack in the way you want, you don't need to use recursion.
Just remove the following lines of code from your function and it will work just fine:
char[] haystackChar = haystack.toCharArray();
char[] newCharArray = new char[(haystackChar.length - 1)];
for(int j = 1; j < haystackChar.length; j++)
{
newCharArray[j - 1] = haystackChar[j];
}
String newStr = new String(newCharArray);
if(doesContain == false)
contains(newStr, needle, doesContain);

I think you are sort of confusing yourself with the recursive function. One of the variables passed to the recursive function is doesContain, but the function is supposed to return whether the string contains it! In the lines
if(doesContain == false)
contains(newStr, needle, doesContain);
The call to contains will return if the substring contains the needle. You need to take that value, and return it back up the call stack.
Hopefully that made some sense. If that didn't, I'll give you the code so you can figure it out yourself:
public static boolean contains(String haystack, String needle)
{
if(haystack.length() < needle.length())
{
return false;
}
else
{
boolean doesContain=false;
for(int i = 0; i < needle.length(); i++)
{
if(haystack.charAt(i) != needle.charAt(i))
if((i + 1) == needle.length())
{
doesContain = false;
break;
}
else
break;
else
if((i + 1) == needle.length())
{
doesContain = true;
break;
}
else
continue;
}
char[] haystackChar = haystack.toCharArray();
char[] newCharArray = new char[(haystackChar.length - 1)];
for(int j = 1; j < haystackChar.length; j++)
{
newCharArray[j - 1] = haystackChar[j];
}
String newStr = new String(newCharArray);
if(doesContain == false)
return contains(newStr, needle);
else
return true;
}
}

Related

Super Reduced String: Problem with my code - Java

My solution for the Super Reduced String problem from HackerRank is giving me problems (https://www.hackerrank.com/challenges/reduced-string/problem). The example "baab" fails the test case, and I do not understand why. Here is my code:
String s = "baab";
for (int i = 0; i < s.length() - 1; i++) {
if (s.charAt(i) == s.charAt(i + 1)) {
s = s.substring(0, i) + s.substring(i + 2);
i = 0;
}
}
if (s.isEmpty()) {
return "Empty String";
} else {
return s;
}
The expected result is "Empty String", but the result is "bb".
Through debug statements it seems like the for loop does not run a third time in this specific scenario (once for "ba"ab, second for b"aa"b, and third for "bb"). Why?
I'll give you a few hints
One loop may not be sufficient
Keeping a certain boolean flag will help you to know when to stop looping
An algorithm isn't the only way to achieve this, you might want to explore another way of manipulating a String
One loop may be sufficient if StringBuilder is used which has method delete(int begin, int end)
Main point is to make a "step back" when duplicate characters are detected and deleted.
Example solution may look as follows:
static String reduce(String str) {
StringBuilder sb = new StringBuilder(str);
int i = 0;
while (i < sb.length()) {
int j = i + 1;
boolean found = false;
if (j < sb.length() && sb.charAt(i) == sb.charAt(j)) {
j++;
found = true;
}
if (found) {
sb.delete(i, j);
i --;
if (i < 0) {
i = 0;
}
} else {
i++;
}
}
if (sb.length() == 0) {
return "Empty String";
}
return sb.toString();
}

Anagram Checking Logical Error - Cant seem to find the error

public class AnagramUnoptimized {
public static void main(String[] args) {
String a = "good";
String b = "ogod";
boolean isAnagram = false;
String c = a.toLowerCase();
String d = b.toLowerCase();
if(c.length()==d.length()) {
boolean [] Visited = new boolean[a.length()];
for (int i = 0; i < c.length(); i++) {
isAnagram = false;
for (int j = 0; j < d.length(); j++) {
if (c.charAt(i) == d.charAt(j) && Visited[j]==false) {
isAnagram = true;
Visited[j] = true;
}
}
if (isAnagram == false) {
break;
}
}
}
if(isAnagram==true){
System.out.println("The given Strings are Anagrams");
}
else{
System.out.println("The given Strings are not Anagrams");
}
}
}
I used a Visited boolean array to check for repeated items but its now showing "Not anagram" for all inputs....
Can you tell me why its showing "Not anagram" if the strings have repeating elements??
The problem with your code is you are continuing with the loop even when visited[j] is changed to true whereas you need to break the inner loop at this point. Do it as follows:
for (int j = 0; j < d.length(); j++) {
if (c.charAt(i) == d.charAt(j) && visited[j] == false) {
isAnagram = true;
visited[j] = true;
break;
}
}
The output after this change:
The given Strings are Anagrams
A better way to do it would be as follows:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String a = "good";
String b = "ogod";
char[] first = a.toLowerCase().toCharArray();
char[] second = b.toLowerCase().toCharArray();
Arrays.sort(first);
Arrays.sort(second);
boolean isAnagram = Arrays.equals(first, second);
if (isAnagram == true) {
System.out.println("The given Strings are Anagrams");
} else {
System.out.println("The given Strings are not Anagrams");
}
}
}
Output:
The given Strings are Anagrams
In your code you should break the inner for loop when the
condition "if (c.charAt(i) == d.charAt(j) && Visited[j]==false)"
has been meet. Because it is still looping through the second stiring and if it will meet the same char one angain it will change the value of Visited[] to true two times, leading to an error. It this example this is the case witch char 'o'. Adding " break; " at the end of the if statement should fix the problem.

Boundary Case conditions for String Anagrams

I trying to write one string Anagram program but stuck while checking the boundary conditions.
I know there are lots of ways and programs available on internet related to String Anagrams using single loops or using collections framework, but I need the solution for my code that how can I involve boundary cases for the code.
public class StringAnagram {
public static void main(String[] args) {
// TODO Auto-generated method stub
String str = "abc";
String strAnagram = "cba";
boolean areAnagrams = ifAnagrams(str, strAnagram);
System.out.println(areAnagrams);
}
private static boolean ifAnagrams(String str, String strAnagram) {
// TODO Auto-generated method stub
int count = 0;
char[] a = strAnagram.toCharArray();
if (str.length() != strAnagram.length()) {
return false;
}
for (int i = 0; i < str.length(); i++) {
{
System.out.println("str.charAt(i) in outer loop :" + str.charAt(i));
for (int j = 0; j < strAnagram.length(); j++) {
if (str.charAt(i) == strAnagram.charAt(j)) {
System.out.println("str.charAt(i) : " + str.charAt(i));
System.out.println("strAnagram.charAt(j) : " + strAnagram.charAt(j));
count++;
}
}
}
System.out.println(count);
if (count == str.length()) {
return true;
}
}
return false;
}
}
Code is working fine if I am inputting the input likes -
"abc" or "abcd" where each char in string is occuring only one time, but it fails when input is like "aab" can be compared to "abc" and it will show strings are anagrams.
So, how this condition I can handle in my code. Please advice.
The problem with your solution is that it only checks if each character in the first string is present in the second string. There are 2 more conditions you need to consider:
If each character in the second string is also present in the first string
If character count for each character in the first and the second string matches
Your current solution will return True for input of ("aaa", "abc") while it should return False. Implementing the first condition I mentioned above will fix this problem.
After you implement the first condition, your solution will return True for input of ("abb", "aab") while it should return False. Implementing the second condition I mentioned above will fix this problem.
Here is a simple way to make this work:
Map<Character, Integer> charCount = new HashMap<Character, Integer>();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (charCount.containsKey(c)) {
charCount.put(c, charCount.get(c)+1);
} else {
charCount.put(c, 1);
}
}
for (int i = 0; i < strAnagram.length(); i++) {
char c = strAnagram.charAt(i);
if (!charCount.containsKey(c)) return false;
if (charCount.get(c) == 0) return false;
charCount.put(c, charCount.get(c)-1);
}
for (char k : charCount.keySet()) {
if (charCount.get(k) != 0) return false;
}
return true;
Since there are no nested loops, the time complexity is O(n). Even though a Map is used, the space complexity is O(1), since it is guaranteed that the total number of keys will not exceed the number of all possible characters.
This solution is even better than sorting in terms of time and space complexity.
This may still be wildly inefficient. Again I apologize for initially overlooking your requirement that no collection frameworks could be included.
public class StringAnagram {
public static void main(String[] args) {
// TODO Auto-generated method stub
// String str = "abc";
// String strAnagram = "cba";
String str = "abcdd";
String strAnagram = "dccba";
boolean areAnagrams = ifAnagrams(str, strAnagram);
System.out.println(areAnagrams);
}
private static boolean ifAnagrams(String str, String strAnagram) {
int count = 0;
char[] a = strAnagram.toCharArray();
char[] b = str.toCharArray();
String alphaString = "abcdefghijklmnopqrstuvwxyz";
char[] alpha = alphaString.toCharArray();
System.out.println(a);
System.out.println(b);
System.out.println("");
if (str.length() != strAnagram.length()) {
return false;
}
for (int i=0; i < alpha.length; i++) {
int countA = 0;
int countB = 0;
for(int j = 0; j < a.length; j++){
if (a[j] == alpha[i]) {
countA++;
}
if (b[j] == alpha[i]) {
countB++;
}
}
if (countA != countB) {
return false;
}
}
return true;
}
}
This alternate solution makes use of a string that contains all the letters in the alphabet, and iterates through them to check if both strings have the same count of each letter. No frameworks this time :)

Java - Boolean method always returning as false

public static boolean isValidNumber(String a1)
{
String x = ("0123456789");
boolean valid = false;
for (int i = 0; i < 4; i++) {
char c = a1.charAt(i);
for (int j = 0; j < 10; j++) {
if ( c == x.charAt(j)) {
valid = true;
}
else {
valid = false;
}
}
}
return valid;
}
The above method checks to see whether an input of a four character string is composed of the characters 0123456789. However, regardless of what the input is, the method always returns as false.
If I were to change the valid value in the else statement to true, the method would always return as true.
What is the error that I have made in this method?
As soon as you find a non matching character, break the loop otherwise the next matching character will set valid to true.
e.g. "123a456" is considered valid.
for (int j = 0; j < 10; j++) {
if ( c == x.charAt(j)) {
valid = true;
}
else {
valid = false;
break;
}
}
If for some reason you don't want to break the loop, you could keep an "invalid counter" and make sure that is 0 at the end.
Of course for what you are doing here, Integer.parseInt() might be your best bet ;-)
a String.equals method will check these two strings in a single statement if you are permitted to use that.
public static boolean isValidNumber(String a1)
{
String x = ("0123456789");
return x.equals(a1);
}
I would rewrite your function as given below,
String x = ("0123456789");
boolean valid = false;
for (int i = 0; i < 4; i++) {
char c = a1.charAt(i);
boolean isCharOK = false;
for (int j = 0; j < 10; j++) {
if ( c == x.charAt(j)) {
isCharOK = true;
break;
}
}
if (!isCharOK) {
valid = false;
break;
}
}
return valid;
John3136 is quite correct, but I would like to propose even better solution to your whole task:
final static String x = "0123456789";
public static boolean isValidNumber(String a1) {
for (int i = 0; i < a1.length(); ++i) {
if (x.indexOf(a1.charAt(i)) == -1) return false;
}
return true;
}
In short: the above code "looks up" every character in your parameter string a1 in the string composed of digits. If it can find it, continues. If it can't, it means a1 consist not only digits and returns false. If it passes through all a1 characters then it returns true :)
And as asked and described in the comments - handling of duplicate characters in argument string:
final static String x = "0123456789";
public static boolean isValidNumber(String a1) {
for (int i = 0; i < a1.length(); ++i) {
final char currentChar = a1.charAt(i);
if (x.indexOf(currentChar) == -1 || a1.indexOf(currentChar, i+1) != -1)
return false;
}
return true;
}
The function call a1.indexOf(currentChar, i+1) essentially checks if there is any duplicate character in the rest of the string (from position i+1 and farther). Which means if it will be able to find duplicate char, the method return false :) Hope this helps, here is more info on String.indexOf(int, int) function if you want:
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#indexOf(int, int)
You can use this one liner function to check for validity of a String as Number using Regular Expression
public static boolean isValidNumber(String a1)
{
return a1.matches("[\\d]+");
}
Hope this helps.

How could I solve this error, with my string to equation convertin calculator?

I'm writing a calculator code that solves the input whats given in string. All is good, except when it gets a negative result in the parentheses it fails badly because two operations get next to each other:
1+2*(10-11) >> 1+2*(-1) >> 1+2*-1
So where *- is, it gets "" (nothing) in the BigDecimal's constructor.
I know what's the problem, but how can I solve it?
import java.math.BigDecimal;
import java.util.ArrayList;
public class DoMath {
public static void main(String[] args) {
// Test equation goes here.
String number = "95.3+43.23*(10-11.1)";
System.out.println(doMath(number));
}
public static BigDecimal doMath(String input) {
StringBuilder builtInput = new StringBuilder(input);
StringBuilder help = new StringBuilder();
// Check if there are parenthesis in the equation.
boolean noParenthesis = true;
for (int i = 0; i < builtInput.length(); i++) {
if (builtInput.charAt(i) == 40) {
noParenthesis = false;
break;
}
}
if (noParenthesis) { // If there are no parenthesis, calculate the equation!
return calculateAndConvert(builtInput);
} else { // If there are parenthesis, breakdown to simple equations!
int parenthesePair = 0;
// Start extracting characters from the builtInput variable.
for (int i = 0; i < builtInput.length(); i++) {
// Start where we find a parentheses opener.
if (builtInput.charAt(i) == 40) {
parenthesePair = 1;
builtInput.deleteCharAt(i);
for (int j = i; j < builtInput.length(); j++) {
// If we find another opener, add one to parenthesePair variable.
if (builtInput.charAt(j) == 40) {
parenthesePair++;
}
// If we find a closer, subtract one from the given variable.
if (builtInput.charAt(j) == 41) {
parenthesePair--;
}
// If we have found the matching pair, delete it and break the for loop.
if (parenthesePair == 0) {
builtInput.deleteCharAt(j);
builtInput.insert(j, doMath(help.toString()));
break;
}
help.append(builtInput.charAt(j));
builtInput.deleteCharAt(j);
j--;
}
break;
}
}
}
System.out.println(builtInput);
return doMath(builtInput.toString());
}
public static BigDecimal calculateAndConvert(StringBuilder input) {
ArrayList<BigDecimal> listOfNumbers = new ArrayList<BigDecimal>();
StringBuilder numBay = new StringBuilder();
StringBuilder operations = new StringBuilder();
// If the first character is -, the first number is negative.
boolean firstIsNegative = false;
if (input.charAt(0) == 45) {
firstIsNegative = true;
input.deleteCharAt(0);
}
// Converting to numbers.
while (input.length() != 0) {
// If the character is a number or a dot, put it in the numBay variable and delete the char.
if (input.charAt(0) >= 48 && input.charAt(0) <= 57 || input.charAt(0) == 46) {
numBay.append(input.charAt(0));
// If the character is not a number, put it in the operations variable
// and save the number in the list (not operator characters are filtered)
} else {
listOfNumbers.add(new BigDecimal(numBay.toString()));
numBay.setLength(0);
operations.append(input.charAt(0));
}
// Delete the character.
input.deleteCharAt(0);
}
listOfNumbers.add(new BigDecimal(numBay.toString()));
// Setting first number to negative if it's needed.
if (firstIsNegative) {
listOfNumbers.set(0, listOfNumbers.get(0).negate());
}
// Calculate the result from the list and operations and return it.
return calculate(listOfNumbers, operations);
}
public static BigDecimal calculate(ArrayList<BigDecimal> list, StringBuilder ops) {
BigDecimal momentaryResult;
// Check for a multiply operation - if there is one, solve it.
for (int i = 0; i < ops.length(); i++) {
if (ops.charAt(i) == 42) {
momentaryResult = list.get(i).multiply(list.get(i + 1));
list.remove(i);
list.set(i, momentaryResult);
ops.deleteCharAt(i);
i--;
}
}
// Check for a divide operation - if there is one, solve it.
for (int i = 0; i < ops.length(); i++) {
if (ops.charAt(i) == 47) {
momentaryResult = list.get(i).divide(list.get(i + 1));
list.remove(i);
list.set(i, momentaryResult);
ops.deleteCharAt(i);
i--;
}
}
// Check for a subtract operation - if there is one, solve it.
for (int i = 0; i < ops.length(); i++) {
if (ops.charAt(i) == 45) {
momentaryResult = list.get(i).subtract(list.get(i + 1));
list.remove(i);
list.set(i, momentaryResult);
ops.deleteCharAt(i);
i--;
}
}
// Check for a plus operation - if there is one, solve it.
for (int i = 0; i < ops.length(); i++) {
if (ops.charAt(i) == 43) {
momentaryResult = list.get(i).add(list.get(i + 1));
list.remove(i);
list.set(i, momentaryResult);
ops.deleteCharAt(i);
i--;
}
}
// Return with the one remaining number that represents the result.
return list.get(0);
}
}
Edit: or would it be easier to write a new code with a different algorithm...?
I would post this as a comment to your question, but I do not have the required reputation to do so.
Anyway, since you have already recognized that the bug is the "operator" *- couldn't you make a method that would fix this problem by replacing the plus operator immediately before by a minus? Like this:
1+2*-1 >>> 1-2*1
If you want I can write you the code. But maybe it will be easier for you to adapt a solution like this in your code that is already working.
Edit - 1:
Obviously, the code should also treat the following cases:
1-2*-1 >>> 1+2*1
2*-1 >>> -2*1
Edit - 2:
Here is the code I managed to make. Let me know if you find any errors.
public int countChar(String str, char chr) {
int count = 0;
for (int k = 0; k < str.length(); k++) {
if (str.charAt(k) == chr)
count++;
}
return count;
}
public String fixBug(String eq) {
boolean hasBug = eq.contains("*-");
if (hasBug) {
String subeq;
int indbug, indp, indm;
eq = eq.replace("*-", "#");
int N = countChar(eq, '#');
for (int k = N; k > 0; k--) {
indbug = eq.indexOf('#');
subeq = eq.substring(0, indbug);
indp = subeq.lastIndexOf('+');
indm = subeq.lastIndexOf('-');
if (indp == -1 && indm == -1) {
eq = "-" + eq;
} else if (indp > indm) {
eq = eq.substring(0, indp) + '-' + eq.substring(indp + 1);
} else {
eq = eq.substring(0, indm) + '+' + eq.substring(indm + 1);
}
}
eq = eq.replace("#", "*");
}
return eq;
}

Categories