1 module fuzzyd; 2 3 import std.stdio; 4 import std.array; 5 import std.container.rbtree; 6 import std.container.binaryheap; 7 import std.ascii; 8 import std.algorithm.iteration; 9 10 alias Result[] delegate(string) scoreFn; 11 alias int function(Input) bonusFn; 12 13 export struct Result { 14 string value; 15 int score; 16 int[] matches; 17 } 18 19 struct Input { 20 string input; 21 string pattern; 22 int col; 23 int row; 24 int[][] scoreMatrix; 25 26 char inputAtIndex() { 27 return input[row]; 28 } 29 30 char patternAtIndex() { 31 return pattern[col]; 32 } 33 34 char isMatch() { 35 return toLower(inputAtIndex) == toLower(patternAtIndex); 36 } 37 38 char isCaseSensitiveMatch() { 39 return isUpper(inputAtIndex) && isUpper(patternAtIndex) && isMatch ? 1 : 0; 40 } 41 } 42 43 int previousCharBonus(Input input) { 44 return (input.col > 0 && input.row > 0) ? 45 2 * input.scoreMatrix[input.row-1][input.col-1] : 0; 46 } 47 48 int startBonus(Input input) { 49 return (input.col == 0 && input.row == 0) ? 1 : 0; 50 } 51 52 int caseMatchBonus(Input input) { 53 return input.isCaseSensitiveMatch ? 1 : 0; 54 } 55 56 int wordBoundaryBonus(Input input) { 57 bool isInputAt = input.row == 0 || 58 input.row == input.input.length-1 || 59 isWhite(input.input[input.row-1]) || 60 isWhite(input.input[input.row+1]); 61 return isInputAt ? 1 : 0; 62 } 63 64 /** 65 * Fuzzy search 66 * Params: 67 * db = Array of string containing the search list. 68 * Examples: 69 * -------------------- 70 * auto f = fuzzy(["foo", "bar", "baz"]); 71 * f("br") 72 * // => [Result("bar", 5, [0, 2]), Result("baz", 3, [0]), Result("foo", 0, [])] 73 * -------------------- 74 */ 75 export scoreFn fuzzy(string[] db) { 76 77 bonusFn[] bonusFns = [&previousCharBonus, &startBonus, &caseMatchBonus, &wordBoundaryBonus]; 78 79 int charScore(Input input) { 80 return input.isMatch ? 81 reduce!((acc, f) => acc + f(input))(1, bonusFns) : 0; 82 } 83 84 Result score(string input, string pattern) { 85 int score = 0; 86 auto matches = redBlackTree!int(); 87 int[][] scoreMatrix = new int[][](input.length, pattern.length); 88 89 for (int col = 0; col < pattern.length; col++) { 90 for (int row = 0; row < input.length; row++) { 91 int charScore = charScore(Input(input, pattern, col, row, scoreMatrix)); 92 if (charScore > 0) matches.insert(row); 93 score += charScore; 94 scoreMatrix[row][col] = charScore; 95 } 96 } 97 98 return Result(input, score, matches.array()); 99 } 100 101 Result[] search(string pattern) { 102 auto maxpq = BinaryHeap!(Result[], "a.score < b.score")(new Result[db.length], 0); 103 foreach(e; db) { 104 maxpq.insert(score(e, pattern)); 105 } 106 return maxpq.array(); 107 } 108 109 return &search; 110 }