Skip to content

Commit be025bc

Browse files
committed
v.3.0.0, contd
* NFA match correctly exact and fuzzy sequence patterns
1 parent 4cf3f40 commit be025bc

11 files changed

Lines changed: 341 additions & 176 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ Exact and fuzzy string searching algorithms for PHP, JavaScript, Python
1717
* [Commentz-Walter Matcher](https://en.wikipedia.org/wiki/Commentz-Walter_algorithm) (TODO)
1818
* [Baeza-Yates-Gonnet Matcher](https://en.wikipedia.org/wiki/Bitap_algorithm) (TODO)
1919
* [Aho-Corasick Matcher](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm) (TODO)
20-
* Generic Finite Automaton that matches [regular expression patterns](https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton), [fuzzy patterns](https://en.wikipedia.org/wiki/Levenshtein_automaton), fuzzy regular expression patterns and combinations of these
20+
21+
22+
A generic **Non-Deterministic Finite Automaton** implementation is also included that can match [exact regular expression patterns](https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton), [fuzzy patterns](https://en.wikipedia.org/wiki/Levenshtein_automaton), fuzzy regular expression patterns and various combinations of these.
23+
2124

2225
**examples:**
2326

src/js/Matchy.js

Lines changed: 120 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Matchy.VERSION = "3.0.0";
2626
Matchy.prototype = {
2727
constructor: Matchy,
2828

29-
fsa: function(pattern, string, offset) {
29+
fsa: function(pattern, string, offset, return_match) {
3030
// https://en.wikipedia.org/wiki/Finite-state_machine
3131
// https://en.wikipedia.org/wiki/String-searching_algorithm
3232
// https://euroinformatica.ro/documentation/programming/!!!Algorithms_CORMEN!!!/DDU0214.html
@@ -59,7 +59,7 @@ Matchy.prototype = {
5959
};
6060

6161
// matcher
62-
var matcher = function(string, offset) {
62+
var matcher = function(string, offset, return_match) {
6363
var n = string.length;
6464
if (null == offset) offset = 0;
6565
if (0 > offset) offset += n;
@@ -69,23 +69,23 @@ Matchy.prototype = {
6969
{
7070
c = string.charAt(i);
7171
q = delta(q, c);
72-
if (m === q) return i-m+1; // matched
72+
if (m === q) return return_match ? pattern : (i-m+1); // matched
7373
}
7474
}
75-
return -1;
75+
return return_match ? null : -1;
7676
};
77-
return null == string ? matcher : matcher(string, offset);
77+
return null == string ? matcher : matcher(string, offset, return_match);
7878
},
7979

80-
rabinkarp: function(pattern, string, offset) {
80+
rabinkarp: function(pattern, string, offset, return_match) {
8181
// https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm
8282
// http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=BA184C94C16CB23D5FA7329E257E3713?doi=10.1.1.86.9502&rep=rep1&type=pdf
8383

8484
var m = pattern.length;
8585
var Q = 3989; // prime number so that 10*Q can fit in one WORD (i.e 2^16 bits)
8686

8787
// matcher
88-
var matcher = function(string, offset) {
88+
var matcher = function(string, offset, return_match) {
8989
var n = string.length;
9090
if (null == offset) offset = 0;
9191
if (0 > offset) offset += n;
@@ -121,7 +121,7 @@ Matchy.prototype = {
121121
// worst case: many hash collisions -> "naive" matching
122122
if (pq === sq)
123123
{
124-
if (string.slice(i, i+m) === pattern) return i; // matched
124+
if (string.slice(i, i+m) === pattern) return return_match ? pattern : i; // matched
125125
}
126126
// update text hash for next char using Horner algorithm
127127
if (i < n)
@@ -131,12 +131,12 @@ Matchy.prototype = {
131131
}
132132
}
133133
}
134-
return -1;
134+
return return_match ? null : -1;
135135
};
136-
return null == string ? matcher : matcher(string, offset);
136+
return null == string ? matcher : matcher(string, offset, return_match);
137137
},
138138

139-
knuthmorrispratt: function(pattern, string, offset) {
139+
knuthmorrispratt: function(pattern, string, offset, return_match) {
140140
// https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
141141
// http://www.eecs.ucf.edu/~shzhang/Combio09/kmp.pdf
142142

@@ -163,7 +163,7 @@ Matchy.prototype = {
163163
}
164164

165165
// matcher
166-
var matcher = function(string, offset) {
166+
var matcher = function(string, offset, return_match) {
167167
var n = string.length;
168168
if (null == offset) offset = 0;
169169
if (0 > offset) offset += n;
@@ -176,7 +176,7 @@ Matchy.prototype = {
176176
{
177177
++i;
178178
++k;
179-
if (k === m) return i - k; // matched
179+
if (k === m) return return_match ? pattern : (i - k); // matched
180180
}
181181
else
182182
{
@@ -189,12 +189,12 @@ Matchy.prototype = {
189189
}
190190
}
191191
}
192-
return -1;
192+
return return_match ? null : -1;
193193
};
194-
return null == string ? matcher : matcher(string, offset);
194+
return null == string ? matcher : matcher(string, offset, return_match);
195195
},
196196

197-
boyermoore: function(pattern, string, offset) {
197+
boyermoore: function(pattern, string, offset, return_match) {
198198
// https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm
199199
// http://www.cs.utexas.edu/~moore/publications/fstrpos.pdf
200200

@@ -228,7 +228,7 @@ Matchy.prototype = {
228228
}
229229

230230
// matcher
231-
var matcher = function(string, offset) {
231+
var matcher = function(string, offset, return_match) {
232232
var n = string.length;
233233
if (null == offset) offset = 0;
234234
if (0 > offset) offset += n;
@@ -244,20 +244,20 @@ Matchy.prototype = {
244244
}
245245
if (0 > j)
246246
{
247-
return i; // matched
247+
return return_match ? pattern : i; // matched
248248
}
249249
else
250250
{
251251
i += shift[j+1];
252252
}
253253
}
254254
}
255-
return -1;
255+
return return_match ? null : -1;
256256
};
257-
return null == string ? matcher : matcher(string, offset);
257+
return null == string ? matcher : matcher(string, offset, return_match);
258258
},
259259

260-
twoway: function(pattern, string, offset) {
260+
twoway: function(pattern, string, offset, return_match) {
261261
// https://en.wikipedia.org/wiki/Two-way_string-matching_algorithm
262262
// http://monge.univ-mlv.fr/~mac/Articles-PDF/CP-1991-jacm.pdf
263263

@@ -326,7 +326,7 @@ Matchy.prototype = {
326326
var small_period = (2*l < m) && (pattern.slice(0, l) === pattern.slice(l, l+p).slice(-l));
327327

328328
// matcher
329-
var matcher = function(string, offset) {
329+
var matcher = function(string, offset, return_match) {
330330
var n = string.length;
331331
if (null == offset) offset = 0;
332332
if (0 > offset) offset += n;
@@ -359,7 +359,7 @@ Matchy.prototype = {
359359
{
360360
--j;
361361
}
362-
if (j <= s) return pos; // matched
362+
if (j <= s) return return_match ? pattern : pos; // matched
363363
pos += p;
364364
s = m-p;
365365
}
@@ -387,36 +387,36 @@ Matchy.prototype = {
387387
{
388388
--j;
389389
}
390-
if (0 === j) return pos; // matched
390+
if (0 === j) return return_match ? pattern : pos; // matched
391391
pos += q;
392392
}
393393
}
394394
}
395395
}
396-
return -1;
396+
return return_match ? null : -1;
397397
};
398-
return null == string ? matcher : matcher(string, offset);
398+
return null == string ? matcher : matcher(string, offset, return_match);
399399
},
400400

401-
commentzwalter: function(pattern, string, offset) {
401+
commentzwalter: function(pattern, string, offset, return_match) {
402402
// https://en.wikipedia.org/wiki/Commentz-Walter_algorithm
403-
return null == string ? non_matcher : non_matcher(string, offset); // TODO
403+
return null == string ? non_matcher : non_matcher(string, offset, return_match); // TODO
404404
},
405405

406-
baezayatesgonnet: function(pattern, string, offset) {
406+
baezayatesgonnet: function(pattern, string, offset, return_match) {
407407
// https://en.wikipedia.org/wiki/Bitap_algorithm
408-
return null == string ? non_matcher : non_matcher(string, offset); // TODO
408+
return null == string ? non_matcher : non_matcher(string, offset, return_match); // TODO
409409
},
410410

411-
ahocorasick: function(pattern, string, offset) {
411+
ahocorasick: function(pattern, string, offset, return_match) {
412412
// https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm
413-
return null == string ? non_matcher : non_matcher(string, offset); // TODO
413+
return null == string ? non_matcher : non_matcher(string, offset, return_match); // TODO
414414
}
415415
};
416416

417-
function non_matcher(string, offset)
417+
function non_matcher(string, offset, return_match)
418418
{
419-
return -1;
419+
return return_match ? null : -1;
420420
}
421421

422422
// non-deterministic finite automaton
@@ -521,7 +521,7 @@ NFA.prototype = {
521521
}
522522
if (',' === type)
523523
{
524-
q = [input[0].q0(), 0];
524+
q = [[input[0].q0(), 0, 0]];
525525
}
526526
return {q:q, e:0}; // keep track of errors
527527
},
@@ -679,34 +679,51 @@ NFA.prototype = {
679679
}
680680
if (',' === type)
681681
{
682-
var i = q[1], q0, q1, e0 = q[0]['e'];
683-
if (input[i].accept(q[0]))
684-
{
685-
if (i+1 < input.length)
682+
var n = input.length,
683+
last_i = 0,
684+
qq = q;
685+
q = [];
686+
qq.forEach(function(qi) {
687+
var i = qi[1],
688+
e0 = qi[0]['e'];
689+
if (input[i].accept(qi[0]))
686690
{
687-
q0 = input[i].d(q[0], c);
688-
q1 = input[i+1].d(input[i+1].q0(), c);
689-
if ((!input[i].reject(q0)) && (input[i+1].reject(q1) || (q0['e'] - e0 < q1['e'])))
691+
if (i+1 < n)
690692
{
691-
q = [q0, i];
692-
e += q0['e'] - e0;
693+
var q0 = input[i].d(qi[0], c),
694+
q1 = input[i+1].d(input[i+1].q0(), c);
695+
if (!input[i].reject(q0))
696+
{
697+
qi = [q0, i, qi[2]];
698+
q.push(qi);
699+
}
700+
if (!input[i+1].reject(q1))
701+
{
702+
++i;
703+
qi = [q1, i, qi[2]+e0];
704+
q.push(qi);
705+
}
693706
}
694707
else
695708
{
696-
q = [q1, i+1];
697-
e += q1['e'];
709+
q.push(qi);
698710
}
699711
}
700712
else
701713
{
702-
//q = q;
714+
qi = [input[i].d(qi[0], c), i, qi[2]];
715+
q.push(qi);
703716
}
704-
}
705-
else
706-
{
707-
q = [input[i].d(q[0], c), i];
708-
e += q[0]['e'] - e0;
709-
}
717+
if (i > last_i)
718+
{
719+
last_i = i;
720+
e = qi[0]['e'] + qi[2];
721+
}
722+
else if (i === last_i)
723+
{
724+
e = stdMath.min(e, qi[0]['e'] + qi[2]);
725+
}
726+
});
710727
}
711728
return {q:q, e:e}; // keep track of errors
712729
},
@@ -729,7 +746,7 @@ NFA.prototype = {
729746
}
730747
else
731748
{
732-
return (e <= type['total_errors']) && input.accept(q);
749+
return input.accept_with_errors(q, type['total_errors']);
733750
}
734751
}
735752
if ('l' === type)
@@ -756,7 +773,10 @@ NFA.prototype = {
756773
}
757774
if (',' === type)
758775
{
759-
return (q[1]+1 === input.length) && input[q[1]].accept(q[0]);
776+
var n = input.length;
777+
return 0 < q.filter(function(qi) {
778+
return (qi[1]+1 === n) && input[qi[1]].accept(qi[0]);
779+
}).length;
760780
}
761781
},
762782

@@ -778,7 +798,7 @@ NFA.prototype = {
778798
}
779799
else
780800
{
781-
return (e > type['total_errors']) || input.reject(q);
801+
return input.reject_with_errors(q, type['total_errors']);
782802
}
783803
}
784804
if ('l' === type)
@@ -805,7 +825,50 @@ NFA.prototype = {
805825
}
806826
if (',' === type)
807827
{
808-
return input[q[1]].reject(q[0]);
828+
return q.length === q.filter(function(qi) {
829+
return input[qi[1]].reject(qi[0]);
830+
}).length;
831+
}
832+
},
833+
834+
accept_with_errors: function(qe, max_errors) {
835+
var self = this, type, input, q, e;
836+
if (!self.accept(qe)) return false;
837+
if (null == max_errors) return true;
838+
type = self.type;
839+
input = self.input;
840+
q = qe['q'];
841+
e = qe['e'];
842+
if (is_obj(type) || (',' !== type))
843+
{
844+
return e <= max_errors;
845+
}
846+
if (',' === type)
847+
{
848+
var n = input.length;
849+
return 0 < q.filter(function(qi) {
850+
return (qi[1]+1 === n) && (qi[0]['e'] + qi[2] <= max_errors) && input[qi[1]].accept(qi[0]);
851+
}).length;
852+
}
853+
},
854+
855+
reject_with_errors: function(qe, max_errors) {
856+
var self = this, type, input, q, e;
857+
if (self.reject(qe)) return true;
858+
if (null == max_errors) return false;
859+
type = self.type;
860+
input = self.input;
861+
q = qe['q'];
862+
e = qe['e'];
863+
if (is_obj(type) || (',' !== type))
864+
{
865+
return e > max_errors;
866+
}
867+
if (',' === type)
868+
{
869+
return q.length === q.filter(function(qi) {
870+
return (qi[0]['e'] + qi[2] > max_errors) || input[qi[1]].reject(qi[0]);
871+
}).length;
809872
}
810873
},
811874

0 commit comments

Comments
 (0)