forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
regularExpressionMatching.js
135 lines (127 loc) · 4.54 KB
/
regularExpressionMatching.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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];
}