mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2026-03-13 08:51:02 +08:00
Add regular expression matching algorithm.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
const ZERO_OR_MORE_CHARS = '*';
|
||||
const ANY_CHAR = '.';
|
||||
|
||||
/**
|
||||
* Dynamic programming approach.
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {string} pattern
|
||||
* @return {boolean}
|
||||
*/
|
||||
export default function regularExpressionMatching(string, pattern) {
|
||||
/*
|
||||
* Let's initiate dynamic programming matrix for this string and pattern.
|
||||
* We will have pattern characters on top (as columns) and string characters
|
||||
* will be placed to the left of the table (as rows).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* a * b . b
|
||||
* - - - - - -
|
||||
* a - - - - - -
|
||||
* a - - - - - -
|
||||
* b - - - - - -
|
||||
* y - - - - - -
|
||||
* b - - - - - -
|
||||
*/
|
||||
const matchMatrix = Array(string.length + 1).fill(null).map(() => {
|
||||
return Array(pattern.length + 1).fill(null);
|
||||
});
|
||||
|
||||
// Let's fill the top-left cell with true. This would mean that empty
|
||||
// string '' matches to empty pattern ''.
|
||||
matchMatrix[0][0] = true;
|
||||
|
||||
// Let's fill the first row of the matrix with false. That would mean that
|
||||
// empty string can't match any non-empty pattern.
|
||||
//
|
||||
// Example:
|
||||
// string: ''
|
||||
// pattern: 'a.z'
|
||||
//
|
||||
// The one exception here is patterns like a*b* that matches the empty string.
|
||||
for (let columnIndex = 1; columnIndex <= pattern.length; columnIndex += 1) {
|
||||
const patternIndex = columnIndex - 1;
|
||||
|
||||
if (pattern[patternIndex] === ZERO_OR_MORE_CHARS) {
|
||||
matchMatrix[0][columnIndex] = matchMatrix[0][columnIndex - 2];
|
||||
} else {
|
||||
matchMatrix[0][columnIndex] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's fill the first column with false. That would mean that empty pattern
|
||||
// can't match any non-empty string.
|
||||
//
|
||||
// Example:
|
||||
// string: 'ab'
|
||||
// pattern: ''
|
||||
for (let rowIndex = 1; rowIndex <= string.length; rowIndex += 1) {
|
||||
matchMatrix[rowIndex][0] = false;
|
||||
}
|
||||
|
||||
// Not let's go through every letter of the pattern and every letter of
|
||||
// the string and compare them one by one.
|
||||
for (let rowIndex = 1; rowIndex <= string.length; rowIndex += 1) {
|
||||
for (let columnIndex = 1; columnIndex <= pattern.length; columnIndex += 1) {
|
||||
// Take into account that fact that matrix contain one extra column and row.
|
||||
const stringIndex = rowIndex - 1;
|
||||
const patternIndex = columnIndex - 1;
|
||||
|
||||
if (pattern[patternIndex] === ZERO_OR_MORE_CHARS) {
|
||||
/*
|
||||
* In case if current pattern character is special '*' character we have
|
||||
* two options:
|
||||
*
|
||||
* 1. Since * char allows it previous char to not be presented in a string we
|
||||
* need to check if string matches the pattern without '*' char and without the
|
||||
* char that goes before '*'. That would mean to go two positions left on the
|
||||
* same row.
|
||||
*
|
||||
* 2. Since * char allows it previous char to be presented in a string many times we
|
||||
* need to check if char before * is the same as current string char. If they are the
|
||||
* same that would mean that current string matches the current pattern in case if
|
||||
* the string WITHOUT current char matches the same pattern. This would mean to go
|
||||
* one position up in the same row.
|
||||
*/
|
||||
if (matchMatrix[rowIndex][columnIndex - 2] === true) {
|
||||
matchMatrix[rowIndex][columnIndex] = true;
|
||||
} else if (
|
||||
(
|
||||
pattern[patternIndex - 1] === string[stringIndex] ||
|
||||
pattern[patternIndex - 1] === ANY_CHAR
|
||||
) &&
|
||||
matchMatrix[rowIndex - 1][columnIndex] === true
|
||||
) {
|
||||
matchMatrix[rowIndex][columnIndex] = true;
|
||||
} else {
|
||||
matchMatrix[rowIndex][columnIndex] = false;
|
||||
}
|
||||
} else if (
|
||||
pattern[patternIndex] === string[stringIndex] ||
|
||||
pattern[patternIndex] === ANY_CHAR
|
||||
) {
|
||||
/*
|
||||
* In case if current pattern char is the same as current string char
|
||||
* or it may be any character (in case if pattern contains '.' char)
|
||||
* we need to check if there was a match for the pattern and for the
|
||||
* string by WITHOUT current char. This would mean that we may copy
|
||||
* left-top diagonal value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* a b
|
||||
* a 1 -
|
||||
* b - 1
|
||||
*/
|
||||
matchMatrix[rowIndex][columnIndex] = matchMatrix[rowIndex - 1][columnIndex - 1];
|
||||
} else {
|
||||
/*
|
||||
* In case if pattern char and string char are different we may
|
||||
* treat this case as "no-match".
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* a b
|
||||
* a - -
|
||||
* c - 0
|
||||
*/
|
||||
matchMatrix[rowIndex][columnIndex] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchMatrix[string.length][pattern.length];
|
||||
}
|
||||
Reference in New Issue
Block a user