Skip to content

Matching brackets

Brackets match when every closer meets the most recent unmatched opener of its kind, which is exactly the top of a stack. One pass, three ways to fail.

Helps to know firstStrings in C, Stacks

Helps to know first
This topic

First, the picture

Does every bracket in {[()()]} close the right one? Watch each opener wait on a stack until its closer arrives.

Checking brackets with a stack

Openers wait on the stack; each closer must meet its own kind

bool balanced(const char *s){    char stack[CAP];    int top = 0;    for (size_t i = 0; s[i] != '\0'; i++) {        char c = s[i];        if (c == '(' || c == '[' || c == '{') {            if (top == CAP)                return false;            stack[top] = c;            top++;        } else if (c == ')' || c == ']' || c == '}') {            if (top == 0)                return false;            top--;            if (!matches(stack[top], c))                return false;        }    }    return top == 0;}int main(void){    const char *cases[4] = { "{[()()]}", "([)]", "(()", "())" };    for (int i = 0; i < 4; i++) {        bool ok = balanced(cases[i]);        printf("%-9s %d\n", cases[i], ok);    }    return 0;}

i is 0: check "{[()()]}".

String

Output

0.00 / 11.80 s
Describe this picture

Lines 11 to 31 (balanced) and 33 to 41 (main) of brackets.c, with the line about to run marked; while matches runs, line 26 is marked. Beside the code is a picture in two parts. At the top, the string s as a row of character boxes, each with its index under it. The character balanced is looking at has a ring around it, which moves along the row. Openers waiting on the stack are tinted. When a closer meets its opener, an arch joins the two boxes and both characters turn violet together. Below is balanced’s char array stack standing upright, stack[0] at the bottom, cells 0 to 3 drawn and one dashed stub for the other sixty. top is a bold line across the array at the boundary above the last opener, labelled “top” and its value; cells at or above top are dimmed but keep their characters. When top— uncovers an opener, that cell is ringed while it is compared with the closer. The clip for {[()()]} plays once, about 12 seconds. {, [ and ( land on the stack one beat each, top climbing to 3. The ) uncovers the (, and the pair () lights up; the next ( lands and the next ) meets it the same way. Then ] meets [ and } meets {, top falls to 0, and the clip rests on “balanced” with the output {[()()]} 1. Under the picture are one sentence on the current step, the string control and the output. The control has the four strings of main: {[()()]}, ([)], (() and ()). Choosing one jumps to that call of balanced and plays it. Where a string fails, the place is ringed with a dashed line and named under it: “wrong kind” under the ) of ([)], which uncovered a [; “left over” under the first ( of ((), still on the stack when the string ends; “nothing to meet” under the last ) of ()), which finds the stack empty. Characters after a failure are never read and stay dim. The transport plays and pauses, steps one beat back or forward, and scrubs; the string and a paused step are kept in the page address as brackets.case and brackets.step.

The most recent opener

Read a string from left to right. Each closer has to close the most recent opener that is still open: in {[()()]}, the first ) closes the ( just before it, the ] closes the [, and the final } closes the { that came first of all. The opener needed next is always the last one seen and not yet closed. That is the top of a stack, as in Stacks.

So the rule is three lines:

  • At an opener, push it.
  • At a closer, pop the top opener and check that it is the same kind.
  • At the end, the stack must be empty.

Other characters, letters, digits, spaces, are skipped.

The code and its cost

The function

bool balanced(const char *s)
{
    char stack[CAP];
    int top = 0;
    for (size_t i = 0; s[i] != '\0'; i++) {
        char c = s[i];
        if (c == '(' || c == '[' || c == '{') {
            if (top == CAP)
                return false;
            stack[top] = c;
            top++;
        } else if (c == ')' || c == ']' || c == '}') {
            if (top == 0)
                return false;
            top--;
            if (!matches(stack[top], c))
                return false;
        }
    }
    return top == 0;
}

The stack is an array of 64 chars inside the function, with top counting the openers on it, exactly as in Stacks. matches(open, close) is true only for the three pairs (), [] and {}. A string nested deeper than 64 is reported unbalanced, because the stack is full (lines 18 and 19).

Three ways to fail

StringWhat happensWhere
([)]) pops [: the wrong kindline 27, at index 2
())the second ) finds the stack emptyline 24, at index 2
(()the string ends with ( still on the stackline 30, top is 1

Each failure has its own line in the code, and the picture marks it on the string.

Why counting is not enough

A first idea is to count: as many ( as ), as many [ as ]. ([)] passes that test, one of each, and it is still wrong: the ) arrives while [ is the open bracket. Counting forgets the order. The stack keeps it.

What it costs

balanced reads each character once and does a fixed few steps for it, so a string of n characters takes about n steps (O(n) in the notation of lesson 3.3). The stack can grow as deep as the string is long: (((( needs four cells.

Running it yourself

gcc -std=c11 -Wall -Wextra -pedantic brackets.c -o brackets
./brackets

It prints each string padded to nine characters, then a space and 1 or 0:

{[()()]}  1
([)]      0
(()       0
())       0

Worked example

a[i] = (b + c) * {d}; The letters, spaces and operators are skipped. [ goes on and ] takes it off; ( on, ) off; { on, } off. The stack is never more than one deep and ends empty: balanced, deepest 1.

if (x[0] == y)) { ( on, [ on, ] meets [, ) meets (, and the stack is empty. The second ) finds nothing to meet: unbalanced at index 14.

{"a": [1, 2, {"b": 3}]} { on, [ on, { on: three deep. Then }, ] and } each meet their own kind: balanced, deepest 3. A real checker for such data would also skip any bracket that appears inside quotes, like the ] in "a]"; this function does not.

Questions. In ((())), how deep does the stack get? Three. Where does {(}) fail? At }: the top of the stack is (.

Where it’s used

Compilers and editors. A compiler checks that every bracket in a program is closed before it goes further, and an editor uses the same stack to light up the bracket that matches the one under the cursor.

Nested data. Formats such as JSON and HTML nest one thing inside another; a reader checks that each part is closed in the right order, as here.

What comes next. Infix to postfix (8.3) uses a stack to turn 3 + 4 * 2 into an order a machine can work out.

Reference card

Code or ideaMeaning
openerpush it
closerpop the top; it must be the same kind
end of stringthe stack must be empty
matches(open, close)true for (), [], {}
wrong kind / empty stack / left overthe three ways to fail
equal countsnot enough: ([)]
costone pass, about n steps (O(n), lesson 3.3); stack up to n deep

Platform: C11, gcc on x86-64 Linux: char 1 byte, int 4, little-endian.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look