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 }