1 module miao..string.not_so_naive;
2 
3 /*!
4 Main features
5     preprocessing phase in constant time and space;
6     searching phase in O(nm) time complexity;
7     slightly (by coefficient) sub-linear in the average case.
8 
9 Description
10     During the searching phase of the Not So Naive algorithm the character comparisons are made with the pattern positions in the following order 1, 2, ... , m-2, m-1, 0.
11     For each attempt where the window is positioned on the text factor y[j .. j+m-1]: if x[0]=x[1] and x[1] neq y[j+1] of if x[0] neq x[1] and x[1]=y[j+1] the pattern is shifted by 2 positions at the end of the attempt and by 1 otherwise.
12     Thus the preprocessing phase can be done in constant time and space. The searching phase of the Not So Naive algorithm has a quadratic worst case but it is slightly (by coefficient) sub-linear in the average case.
13 */
14 
15 @trusted:
16 
17 import miao.common.check;
18 import miao.common.util;
19 
20 struct Not_so_naive_searcher(PatRange, CorpusRange = PatRange) 
21     if (isValidParam!(PatRange, CorpusRange)) {
22 public:
23     this(in PatRange pattern)
24     {
25         pattern_ = pattern;
26         if (pattern_.length >= 2) {
27             if (pattern_[0] == pattern_[1]) {
28                 skip_ = 2;
29                 slide_ = 1;
30             }
31             else {
32                 skip_ = 1;
33                 slide_ = 2;
34             }
35         }
36     }
37 
38     int search(in CorpusRange corpus) const
39     out(result) {
40             assert(result == -1 || (0 <= result && result < corpus.length));
41     }
42 	body {
43 		if (corpus.length == 0 || pattern_.length == 0) return -1;
44 		if (corpus.length < pattern_.length) return -1;
45         if (pattern_.length == 1) {
46             return pattern_[0] == corpus[0]? 0: -1;
47         }
48 		return search_(corpus);
49 	}
50 
51 private:
52     int search_(in CorpusRange corpus) const
53     in {
54         assert(corpus.length >= 2);
55         assert(pattern_.length >= 2);
56     }
57     body {
58         const cmp_len = corpus.length - pattern_.length;
59 
60         for (auto window_pos = 0; window_pos <= cmp_len; ) {
61             const window = corpus[window_pos .. window_pos + pattern_.length];
62             if (window[1] != pattern_[1]) {
63                 window_pos += skip_;
64             }
65             else {
66                 if (window == pattern_) return window_pos;
67                 window_pos += slide_;
68             }
69         }
70 
71         return -1;
72     }
73     
74 private:
75     const PatRange pattern_;
76     immutable uint skip_;
77     immutable uint slide_;
78 }
79 
80 alias not_so_naive_search = GenerateFunction!Not_so_naive_searcher;
81 
82 unittest {
83     import miao.string.test;
84     
85     testAll!(Not_so_naive_searcher, not_so_naive_search)();
86 }