snippetjavascriptTip
Find matching bracket pairs in a string with JavaScript
Viewed 0 times
javascriptpairsfindmatchingwithbracketstring
Problem
I was recently honing my skills over at CodeWars and I came across an <dfn title="Short for esoteric programming language; a computer programming language designed to experiment with unconventional ideas, be difficult to program in, or serve as a joke, rather than for practical use.">Esolang</dfn> interpreter kata, that, ultimately, required me to find matching bracket pairs in a string. I'll spare you the details of the problem for now (maybe I'll write about it in the future), but I thought I'd share the bracket matching solution with you, as I've found it to be a recurring problem when building parsers or solving similar problems.
> [!NOTE]
>
> I'm sure that searching for this problem will yield lots of results, mostly variations of the same sort of solution, many of which contain no comments or explanations. I'll try to explain the solution as best as I can, starting from simple parentheses matching and then moving on to more complex bracket pairs.
Given a simple string with parentheses, like
> [!NOTE]
>
> I'm sure that searching for this problem will yield lots of results, mostly variations of the same sort of solution, many of which contain no comments or explanations. I'll try to explain the solution as best as I can, starting from simple parentheses matching and then moving on to more complex bracket pairs.
Given a simple string with parentheses, like
((a + b)), we want to find the matching pairs of parentheses. In this case, the pairs are (0, 8) and (1, 7). The first pair is the outermost parentheses, and the second pair is the innermost parentheses.Solution
const findMatchingParentheses = str => {
const { stack, pairs } = [...str].reduce(
({ pairs, stack }, char, i) => {
if (char === '(') stack.push(i);
else if (char === ')') {
if (stack.length === 0) throw new Error('Invalid string');
pairs.push([stack.pop(), i]);
}
return { pairs, stack };
},
{ pairs: [], stack: [] }
);
if (stack.length > 0) throw new Error('Invalid string');
return pairs;
};
findMatchingParentheses('a + b'); // []
findMatchingParentheses('(a + b)'); // [[0, 6]]
findMatchingParentheses('((a + b))'); // [[1, 7], [0, 8]]
findMatchingParentheses('((a + b)'); // Error: Invalid string
findMatchingParentheses('a + b)'); // Error: Invalid string>
> I'm sure that searching for this problem will yield lots of results, mostly variations of the same sort of solution, many of which contain no comments or explanations. I'll try to explain the solution as best as I can, starting from simple parentheses matching and then moving on to more complex bracket pairs.
Given a simple string with parentheses, like
((a + b)), we want to find the matching pairs of parentheses. In this case, the pairs are (0, 8) and (1, 7). The first pair is the outermost parentheses, and the second pair is the innermost parentheses.Looking closely, we can identify the pattern we're looking for. If we come across an opening parenthesis, we have to store its index. When we find a closing parenthesis, we can pair it with the last opening parenthesis we found. This way, we can find the matching pairs in the given string.
If, however, we find a closing parenthesis without a matching opening parenthesis, we can safely assume that the string is invalid. Similarly, if we find an opening parenthesis without a matching closing parenthesis, the string is also invalid.
To solve this problem, we can use a stack-based approach. As we'll only need its
push() and pop() methods, we can use a simple array to represent the stack. Let's see how we can implement this in JavaScript:Code Snippets
const findMatchingParentheses = str => {
const { stack, pairs } = [...str].reduce(
({ pairs, stack }, char, i) => {
if (char === '(') stack.push(i);
else if (char === ')') {
if (stack.length === 0) throw new Error('Invalid string');
pairs.push([stack.pop(), i]);
}
return { pairs, stack };
},
{ pairs: [], stack: [] }
);
if (stack.length > 0) throw new Error('Invalid string');
return pairs;
};
findMatchingParentheses('a + b'); // []
findMatchingParentheses('(a + b)'); // [[0, 6]]
findMatchingParentheses('((a + b))'); // [[1, 7], [0, 8]]
findMatchingParentheses('((a + b)'); // Error: Invalid string
findMatchingParentheses('a + b)'); // Error: Invalid stringconst findMatchingBrackets = str => {
const bracketMap = {
'(': ')',
'[': ']',
'{': '}',
};
const bracketOpenings = Object.keys(bracketMap);
const bracketClosings = Object.values(bracketMap);
const { stack, pairs } = [...str].reduce(
({ pairs, stack }, char, i) => {
if (bracketOpenings.includes(char)) stack.push(i);
else if (bracketClosings.includes(char)) {
if (stack.length === 0) throw new Error('Invalid string');
const openingIndex = stack.pop();
if (bracketMap[str[openingIndex]] !== char)
throw new Error('Invalid string');
pairs.push([openingIndex, i]);
}
return { pairs, stack };
},
{ pairs: [], stack: [] }
);
if (stack.length > 0) throw new Error('Invalid string');
return pairs;
};
findMatchingBrackets('a + b'); // []
findMatchingBrackets('(a + b)'); // [[0, 6]]
findMatchingBrackets('((a + b))'); // [[1, 7], [0, 8]]
findMatchingBrackets('([a] + b)'); // [[1, 3], [0, 8]]
findMatchingBrackets('([{a} + b])'); // [[2, 4], [1, 9], [0, 10]]
findMatchingBrackets('((a + b)'); // Error: Invalid string
findMatchingBrackets('a + b)'); // Error: Invalid string
findMatchingBrackets('([a + b}'); // Error: Invalid stringconst findMatchingBrackets = str => {
const bracketMap = {
'(': ')',
'[': ']',
'{': '}',
};
const bracketOpenings = Object.keys(bracketMap);
const bracketClosings = Object.values(bracketMap);
const { stack, pairs } = [...str].reduce(
({ pairs, stack }, char, i) => {
if (bracketOpenings.includes(char)) stack.push(i);
else if (bracketClosings.includes(char)) {
if (stack.length === 0) throw new Error('Invalid string');
const openingIndex = stack.pop();
if (bracketMap[str[openingIndex]] !== char)
throw new Error('Invalid string');
pairs.set(openingIndex, i);
pairs.set(i, openingIndex);
}
return { pairs, stack };
},
{ pairs: new Map(), stack: [] }
);
if (stack.length > 0) throw new Error('Invalid string');
return pairs;
};
const str = '([{a} + b])';
const pairs = findMatchingBrackets(str);
[...str].forEach((char, i) => {
if (pairs.has(i)) {
const matchingIndex = pairs.get(i);
const matchingChar = str[pairs.get(i)];
console.log(
`Bracket "${char}" at index ${i} matches with bracket "${matchingChar}" at index ${matchingIndex}`
);
}
});
// Bracket "(" at index 0 matches with bracket ")" at index 10
// Bracket "[" at index 1 matches with bracket "]" at index 9
// Bracket "{" at index 2 matches with bracket "}" at index 4
// Bracket "}" at index 4 matches with bracket "{" at index 2
// Bracket "]" at index 9 matches with bracket "[" at index 1
// Bracket ")" at index 10 matches with bracket "(" at index 0Context
From 30-seconds-of-code: find-matching-bracket-pairs
Revisions (0)
No revisions yet.