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 "{[()()]}".
Output
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
| String | What happens | Where |
|---|---|---|
([)] | ) pops [: the wrong kind | line 27, at index 2 |
()) | the second ) finds the stack empty | line 24, at index 2 |
(() | the string ends with ( still on the stack | line 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 idea | Meaning |
|---|---|
| opener | push it |
| closer | pop the top; it must be the same kind |
| end of string | the stack must be empty |
matches(open, close) | true for (), [], {} |
| wrong kind / empty stack / left over | the three ways to fail |
| equal counts | not enough: ([)] |
| cost | one 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.