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) 19 { 20 return lidx.length > 0 && lidx[$ - 1] == cidx - 1 ? 20 : 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 if (pattern == "") 62 return FuzzyResult(txt, 0, [], true); 63 64 const patternLength = pattern.length; 65 long score = 0; 66 bool start, end; 67 68 long[] lidx; 69 long j = 0; 70 long i = 0; 71 foreach (t; txt.byCodePoint) 72 { 73 if (t.toLower == pattern[j].toLower) 74 { 75 score += 10; 76 score += firstIdx(i) + consecutiveBonus(lidx, i) + caseMatchBonus(t, pattern[j]); 77 if (j == 0) 78 start = true; 79 80 j++; 81 lidx ~= i; 82 if (j == patternLength) 83 { 84 end = true; 85 break; 86 } 87 } 88 i++; 89 } 90 if (start && end) 91 { 92 score += 1000; 93 } 94 return FuzzyResult(txt, score, lidx, start && end); 95 } 96 97 long search(string pattern, ref FuzzyResult[] result) 98 { 99 long totalMatches = 0; 100 auto p = pattern.byCodePoint.array; 101 for (long i = 0, max = result.length; i < max; i++) 102 { 103 result[i] = score(db[i], p); 104 if (result[i].isMatch) 105 totalMatches++; 106 } 107 return totalMatches; 108 } 109 110 return &search; 111 }