I\'ve written software in the past that uses a stack to check for balanced equations, but now I\'m asked to write a similar algorithm recursively to check for properly neste
I would say this depends on your design. You could either use two counters or stack with two different symbols or you can handle it using recursion, the difference is in design approach.
There are many ways to do this, but the simplest algorithm is to simply process forward left to right, passing the stack as a parameter
FUNCTION isBalanced(String input, String stack) : boolean
IF isEmpty(input)
RETURN isEmpty(stack)
ELSE IF isOpen(firstChar(input))
RETURN isBalanced(allButFirst(input), stack + firstChar(input))
ELSE IF isClose(firstChar(input))
RETURN NOT isEmpty(stack) AND isMatching(firstChar(input), lastChar(stack))
AND isBalanced(allButFirst(input), allButLast(stack))
ELSE
ERROR "Invalid character"
Here it is implemented in Java. Note that I've switched it now so that the stack pushes in front instead of at the back of the string, for convenience. I've also modified it so that it just skips non-parenthesis symbols instead of reporting it as an error.
static String open = "([<{";
static String close = ")]>}";
static boolean isOpen(char ch) {
return open.indexOf(ch) != -1;
}
static boolean isClose(char ch) {
return close.indexOf(ch) != -1;
}
static boolean isMatching(char chOpen, char chClose) {
return open.indexOf(chOpen) == close.indexOf(chClose);
}
static boolean isBalanced(String input, String stack) {
return
input.isEmpty() ?
stack.isEmpty()
: isOpen(input.charAt(0)) ?
isBalanced(input.substring(1), input.charAt(0) + stack)
: isClose(input.charAt(0)) ?
!stack.isEmpty() && isMatching(stack.charAt(0), input.charAt(0))
&& isBalanced(input.substring(1), stack.substring(1))
: isBalanced(input.substring(1), stack);
}
Test harness:
String[] tests = {
"()[]<>{}",
"(<",
"]}",
"()<",
"(][)",
"{(X)[XY]}",
};
for (String s : tests) {
System.out.println(s + " = " + isBalanced(s, ""));
}
Output:
()[]<>{} = true
(< = false
]} = false
()< = false
(][) = false
{(X)[XY]} = true
The more intuitive solution is to use stack like so:
function isBalanced(str) {
const parentesis = {
'(': ')',
'[': ']',
'{': '}',
};
const closing = Object.values(parentesis);
const stack = [];
for (let char of str) {
if (parentesis[char]) {
stack.push(parentesis[char]);
} else if (closing.includes(char) && char !== stack.pop()) {
return false;
}
}
return !stack.length;
}
console.log(isBalanced('{[()]}')); // true
console.log(isBalanced('{[(]]}')); // false
console.log(isBalanced('([()]')); // false
And using recursive function (without using stack), might look something like so:
function isBalanced(str) {
const parenthesis = {
'(': ')',
'[': ']',
'{': '}',
};
if (!str.length) {
return true;
}
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (parenthesis[char]) {
for (let j = str.length - 1; j >= i; j--) {
const _char = str[j];
if (parenthesis[_char]) {
return false;
} else if (_char === parenthesis[char]) {
return isBalanced(str.substring(i + 1, j));
}
}
} else if (Object.values(parenthesis).includes(char)) {
return false;
}
}
return true;
}
console.log(isBalanced('{[()]}')); // true
console.log(isBalanced('{[(]]}')); // false
console.log(isBalanced('([()]')); // false
* As @Adrian mention, you can also use stack in the recursive function without the need of looking backwards
It doesn't really matter from a logical point of view -- if you keep a stack of all currently un-balanced parens that you pass to each step of the recursion, you'll never need to look backwards, so it doesn't matter if you cut up the string on each recursive call, or just increment an index and only look at the current first character.
In most programming languages, which have non-mutable strings, it's probably more expensive (performance-wise) to shorten the string than it is to pass a slightly larger string on the stack. On the other hand, in a language like C, you could just increment a pointer within the char array. I guess it's pretty language-dependent which of these two approaches is more 'efficient'. They're both equivalent from a conceptual point of view.
In the Scala programming language, I would do it like this:
def balance(chars: List[Char]): Boolean = {
def process(chars: List[Char], myStack: Stack[Char]): Boolean =
if (chars.isEmpty) myStack.isEmpty
else {
chars.head match {
case '(' => process(chars.tail, myStack.push(chars.head))
case ')' => if (myStack.contains('(')) process(chars.tail, myStack.pop)
else false
case '[' => process(chars.tail, myStack.push(chars.head))
case ']' => {
if (myStack.contains('[')) process(chars.tail, myStack.pop) else false
}
case _ => process(chars.tail, myStack)
}
}
val balancingAuxStack = new Stack[Char]
process(chars, balancingAuxStack)
}
Please edit to make it perfect.
I was only suggesting a conversion in Scala.
The idea is to keep a list of the opened brackets, and if you find a closing brackt, check if it closes the last opened:
When the string is finally empty, if the list of brackes is empty too (so all the brackes has been closed) return true
, else false
ALGORITHM (in Java):
public static boolean isBalanced(final String str1, final LinkedList<Character> openedBrackets, final Map<Character, Character> closeToOpen) {
if ((str1 == null) || str1.isEmpty()) {
return openedBrackets.isEmpty();
} else if (closeToOpen.containsValue(str1.charAt(0))) {
openedBrackets.add(str1.charAt(0));
return isBalanced(str1.substring(1), openedBrackets, closeToOpen);
} else if (closeToOpen.containsKey(str1.charAt(0))) {
if (openedBrackets.getLast() == closeToOpen.get(str1.charAt(0))) {
openedBrackets.removeLast();
return isBalanced(str1.substring(1), openedBrackets, closeToOpen);
} else {
return false;
}
} else {
return isBalanced(str1.substring(1), openedBrackets, closeToOpen);
}
}
TEST:
public static void main(final String[] args) {
final Map<Character, Character> closeToOpen = new HashMap<Character, Character>();
closeToOpen.put('}', '{');
closeToOpen.put(']', '[');
closeToOpen.put(')', '(');
closeToOpen.put('>', '<');
final String[] testSet = new String[] { "abcdefksdhgs", "[{aaa<bb>dd}]<232>", "[ff{<gg}]<ttt>", "{<}>" };
for (final String test : testSet) {
System.out.println(test + " -> " + isBalanced(test, new LinkedList<Character>(), closeToOpen));
}
}
OUTPUT:
abcdefksdhgs -> true
[{aaa<bb>dd}]<232> -> true
[ff{<gg}]<ttt> -> false
{<}> -> false
Note that i have imported the following classes:
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;