how to check unbalanced brackets in python 3?

Viewed 4906

How to check unbalanced brackets? I am considering brackets --> {, [, (, ), ], }. How can I check if a string is unbalanced?

Examples:

  1. {[()]} --> Balanced
  2. {[(])} --> Unbalanced
  3. {{[[(())]]}} --> Balanced

I was trying this way, but it's not working:

string = '{[(])}'

par = 0
col = 0
cha = 0
for i in string:
    if i == '{':
        cha+=1
    if i == '}':
        cha-=1
    if i == '[':
        col+=1
    if i == ']':
        col-=1
    if i == '(':
        par+=1
    if i == ')':
        par-=1
    if (cha < 0) or (col < 0) or (par < 0):
        print('Unbalanced')
        break
if (cha != 0) or (col != 0) or (par != 0):
    print('Unbalanced')
else:
    print('Balanced')
9 Answers

I don't think you can verify the balancedness of a string just by keeping track of how many of each character you've seen so far. It matters what order you saw them in too, so you've got to incorporate that statefulness one way or the other. Here's one approach -- keep a stack of "unmatched" brackets. Add to it when you find a new left bracket, pop off of it when you find a right bracket that matches the most recent left bracket, and return False if you expect a match but don't get one. If you reach the end of the list and the stack is empty, then the string is balanced.

def balanced(s):
    pairs = {"{": "}", "(": ")", "[": "]"}
    stack = []
    for c in s:
        if c in "{[(":
            stack.append(c)
        elif stack and c == pairs[stack[-1]]:
            stack.pop()
        else:
            return False
    return len(stack) == 0


test_cases = ("{[()]}", "{[(])}", "{{[[(())]]}}")
for s in test_cases:
    print(s, balanced(s))

Result:

{[()]} True
{[(])} False
{{[[(())]]}} True

You have to walk through the string and store the incoming chars. Every time the last two chars are {}, [], (), just delete those chars and match the new char against the last stored (and not deleted). Continue until you reach the end of the string: if you deleted all chars, the string is balanced.

I find it easier to write with a fold:

>>> from functools import reduce
>>> def f(s): return reduce(lambda acc, x: acc[:-1] if acc and acc[-1]+x in ('{}', '[]', '()') else acc+x, s)

Tests cases:

>>> f('{[()]}')
'' # balanced
>>> f('{[(])}')
'{[(])}' # unbalanced

For a imperative version, see @Kevin's answer (the two branches of my if are equivalent to pop/push on the stack).

EDIT if you are looking for pseudo-palindrom ({[]} is balanced but not {}[]), you can match each char with the char at the same position beginning from the end.

First, check if the string has an even number of chars:

>>> s = '{[()]}'
>>> len(s) % 2
0

If that's the case, match the chars:

>>> t, u = s[:len(s)//2], s[len(s)//2:]
>>> t, u
('{[(', ')]}')
>>> t, "".join(reversed(u))
('{[(', '}])')
>>> [o+c for o,c in zip(t, reversed(u))]
['{}', '[]', '()']

The string is balanced iff you have only matching pairs:

>>> [o+c for o,c in zip(t, reversed(u)) if o+c not in ('{}', '[]', '()')]
[] # balanced

Create a stack. Every time you encounter:

  • a left parenthesis
  • a left curly brace
  • a left-bracket
  • a left-anything

... push that left "delimiter" onto the stack. When you encounter a right-delimiter, pop the topmost element off the stack. The popped element will be the most recently seen left delimiter. Make sure that the left and right delimiters match. e.g. they are both parentheses, or both braces.

If the current left delimiter on the stack is a brace and the current right delimiter is a parenthesis, return the message that things are unbalanced.

If you ever try to pop something off an already empty stack, return "unbalanced."

If you read-in the entire string, but the stack isn't empty by the time you reach the end, return "unbalanced."

The above cover all of the cases in which the string is unbalanced.

If you read in the entire string and...

  • the stack is empty when you are done
  • you never tried to pop an empty stack
  • the left delimiter on the stack always matched the right delimiter encountered (e.g. both were parentheses)

then the string is balanced. return "balanced"

Here is an alternative code to check for unbalanced brackets:

def is_matched(expression):
    """
    Finds out how balanced an expression is.
    With a string containing only brackets.

    >>> is_matched('[]()()(((([])))')
    False
    >>> is_matched('[](){{{[]}}}')
    True
    """
    opening = tuple('({[')
    closing = tuple(')}]')
    mapping = dict(zip(opening, closing))
    queue = []

    for letter in expression:
        if letter in opening:
            queue.append(mapping[letter])
        elif letter in closing:
            if not queue or letter != queue.pop():
                return False
    return not queue

if __name__ == '__main__':
    import doctest
    doctest.testmod()

I would just strip out balanced chunks of the string until I'm left with nothing or an unbalanced string that cannot be further reduced:

def is_matched(expr):
    expr = re.sub("[^][}{)(]+", "", expr)
    while expr:
        expr1 = re.sub(r"\(\)|\[\]|\{\}", "", expr)
        if expr1 == expr:
            return not expr1
        expr = expr1
    return True

>>> is_matched("{[()]}")
True
>>> is_matched("{[(])}")
False
>>> is_matched("{{[[(())]]}}")
True

The first re.sub() removed anything that isn't a parenthesis. The loop then removes all adjacent open/close pairs and repeats until we run out of string or we run out of adjacent pairs.

def paranthesis(exprs):
    stack = []
    for char in exprs:
        if char in ["(","{","["]:
            stack.append(char)
        else:
            currchar = stack.pop()
            if currchar =="(":
                if char !=")":
                    return False
            if currchar == "{":
                if char != "}":
                    return False
            if currchar == "[":
                if char != "]":
                    return False
    if stack:
        print(stack)
        return False
    return True


if __name__ == "__main__":
    exprs = "({[]}]"

    if paranthesis:
        print("Balanced")
    else:
        print("Unbalanced")
}

If the first and last characters are a matched pair, peel them off. Repeat.

If you ever get into a state where the first and last characters aren't a matched pair, the string was imbalanced.

EDIT: This won't work, as pointed out by @blhsing.

New approach: Look for {}, [], or (), and remove it. Repeat until no more are found. If the string is empty, it was balanced, otherwise it wasn't.

Here's a stack based approach that converts the group characters to positive/negative numbers to make the rest of the logic independent of character processing (and allowing other characters in the string):

def isBalanced(s):
    opened = [0] # {}=3/-3, []=2/-2, ()=1/-1, others:0/-4
    for n in [3-("{[( )]}"+c).index(c) for c in s]:
        if not n&3 : continue
        elif n>0   : opened.append(n)
        elif opened.pop() != -n: return False
    return opened == [0]

coming to @kevin 's answer. The following passes all his test cases but it is simpler.

def balanced(s):
    if s.count('[') == s.count(']') and s.count('(') == s.count(')') and s.count('{') == s.count('}'):
        return True
    else:   
        return False

The goal to ask such a question is not just to see the count but whether the brackets as a whole make sense. If it is asked in an interview you would like to let them know that you understand the motive behind it.

Prob_test_cases = ('}{', '][', ')(')

Most solutions will pass the above as balanced or parenthesis match but ideally they should fail. You do not encode your code or string like that in Prob_test_cases. Here is what I have. long but understandable without any stack, queue or data structure implementation.

def para_check(checkme):
    open = ['(', '[', '{']
    close = [')', ']', '}']
    # assume that the result is true
    result = True

    # if the input is not a list then convert it to list
    if type(checkme) is not list:
        checkme = list(checkme)

    # if it doesnt contain at least 2 elements then return false
    if len(checkme) < 2:
        result = False

    # if number of closing and opening paranthesis is not the same then it is not balanced
    count_check1 = checkme.count('[') == checkme.count(']')
    count_check2 = checkme.count('(') == checkme.count(')')
    count_check3 = checkme.count('{') == checkme.count('}')

    # if not all of the above are true then it is unbalanced and thus...
    if not all([count_check1, count_check2, count_check3]):
        result = False

    def recurser(checkme, first, last):
        '''
        here start can be '[,(,{' and end can be '],),}' respectively,
        Check for a given type of bracket (any 1 of 3) see if the 
        index of the first closing bracket is greater than the first 
        opening bracket and if yes then remove them since they are a pair.
        Repeat this forever for all 3 types of brackets.
        '''            
        if first in checkme and last in checkme:
            open_index = checkme.index(first)
            closed_index = checkme.index(last)
            
            if closed_index > open_index:
                checkme.pop(closed_index)
                checkme.pop(open_index)
                # recursion
                recurser(checkme, first, last)
            else:
                result = False

    recurser(checkme, '[', ']')
    recurser(checkme, '(', ')')
    recurser(checkme, '{', '}')

    if len(checkme) > 0:
        result = False

    return result
Related