1 module fuzzyd.core;
2 
3 import std.stdio;
4 import std.array;
5 import std.range;
6 import std.math;
7 import std.conv;
8 import std.uni;
9 import std.algorithm.iteration;
10 import std.algorithm.sorting;
11 import std.algorithm : max;
12 import std.typecons;
13 
14 alias fuzzyFn = long delegate(string, ref FuzzyResult[]);
15 
16 private:
17 
18 long consecutiveBonus(ref long[] lidx, long cidx, long score)
19 {
20     return lidx.length > 0 && lidx[$-1] == cidx ? score * 2 : 0;
21 }
22 
23 long caseMatchBonus(dchar x, dchar y)
24 {
25     return x.isUpper && y.isUpper ? 15 : 0;
26 }
27 
28 long firstIdx(long idx)
29 {
30     return idx == 0 ? 10 : 0;
31 }
32 
33 public:
34 
35 /// fuzzy search result
36 struct FuzzyResult
37 {
38     string value; /// entry. e.g "Documents/foo/bar/"
39     long score; /// similarity metric. (Higher better)
40     long[] matches; /// index of matched characters (0 = miss , 1 = hit).
41     bool isMatch; /// return true if consecutive characters are matched.
42 }
43 
44 /**
45  * Fuzzy search
46  * Params:
47  *   db = Array of string containing the search list.
48  * Examples:
49  * --------------------
50  * auto result = new FuzzyResult[3];
51  * fuzzy(["foo", "bar", "baz"])("br", result);
52  * // => result
53    // [FuzzyResult("foo", 0, [], false), FuzzyResult("bar", 1030, [0, 2], true), FuzzyResult("baz", 20, [0], false)]
54  * --------------------
55  */
56 fuzzyFn fuzzy(string[] db)
57 {
58 
59     FuzzyResult score(ref string txt, ref dchar[] pattern)
60     {
61         const patternLength = pattern.length; 
62         long score = 0;
63         bool start, end;
64 
65         long[] lidx;
66         long j = 0;
67         long i = 0;
68         foreach (t; txt.byCodePoint)
69         {
70             if (t.toLower == pattern[j].toLower)
71             {
72                 score += 10;
73                 score += firstIdx(i) + consecutiveBonus(lidx, i, score) + caseMatchBonus(t, pattern[j]);
74                 if (j == 0)
75                     start = true;
76                 
77                 j++;
78                 lidx ~= i;
79                 if (j == patternLength) {
80                     end = true;
81                     break;
82                 }
83             }
84             i++;
85         }
86         if (start && end) {
87             score += 1000;
88         }
89         return FuzzyResult(txt, score, lidx, start && end);
90     }
91 
92     long search(string pattern, ref FuzzyResult[] result)
93     {
94         long totalMatches = 0;
95         auto p = pattern.byCodePoint.array;
96         for (long i = 0, max = result.length; i < max; i++)
97         {
98             result[i] = score(db[i], p);
99             if (result[i].isMatch)
100                 totalMatches++;
101         }
102         return totalMatches;
103     }
104 
105     return &search;
106 }