1 /** 2 * Spelling suggestion based on the Spelling Corrector found at: 3 * norvig.com/spell-correct.html 4 * 5 * A word list, big.txt can also be downloaded from the website: 6 * norvig.com/big.txt 7 * 8 * Synopsis: 9 * -------- 10 * buildList(std.file.readText("big.txt").replace(regex(".?!"), " ").split); 11 * 12 * if(misspelled(args[1])) 13 * std.stdio.writeln(giveWord(args[1])); 14 * -------- 15 */ 16 17 module spelling.suggestion; 18 19 import std.algorithm; 20 import std.array; 21 import std.file; 22 import std.functional; 23 import std.regex; 24 import std.string; 25 static import std.math; 26 27 private int[string] model; 28 /** 29 * Give a suggested word based the given word. 30 * 31 * The selection is based on the fequency of words per the training 32 * and the edit distance of the given word to the suggestion. 33 * If there are no known words within an edit distance of two 34 * the given word is returned unchanged. 35 * 36 * The word with the fewest edits is returned unless the predicate is true. 37 * Where a is the fequency of edit distance one and b is the frequency of 38 * edit distance two. 39 */ 40 public string giveWord(alias predicate = "a <= 1 && b > 4")(string word) { 41 auto candy = candidates(word); 42 if(candy[0].empty && candy[1].empty) return word; 43 44 auto ld1 = minPos!((a,b) => model[a] > model[b])(candy[0]); 45 auto ld2 = minPos!((a,b) => model[a] > model[b])(candy[1]); 46 if(ld1.empty) return ld2.front; 47 if(ld2.empty) return ld1.front; 48 49 if(binaryFun!predicate(model[ld1.front], model[ld2.front])) 50 return ld2.front; 51 52 return ld1.front; 53 } 54 55 unittest { 56 scope(exit) model = null; 57 buildList(["hello","hello","held","hells","electronic"]); 58 auto hello = giveWord("helo"); 59 assert(hello == "hello"); 60 auto electronic = giveWord("ellectonic"); 61 assert(electronic == "electronic"); 62 auto uknwon = giveWord("uknwon"); 63 assert(uknwon == "uknwon"); 64 } 65 66 /** 67 * Takes a Range of words and adds them to the dictionary 68 * while at the same time increasing frequency of use. 69 * 70 * This is similar to training the dictionary, but it assumes 71 * all words are spelled correctly. 72 */ 73 public void buildList(Range)(Range words) { 74 foreach(w; words) 75 model[w] += 1; 76 } 77 78 /** 79 * Takes a list of words and increases the frequency of use 80 * only if the word is spelled correctly 81 * 82 * You must first add words with buildList for this to work 83 * it does not add new words to the list. 84 */ 85 public void train(Range)(Range words) { 86 foreach(w; words) 87 if(!misspelled(w)) 88 model[w] += 1; 89 } 90 91 /// Tells if a given word is misspelled 92 public bool misspelled(string word) { 93 if(word.empty) return false; 94 if(word in model) 95 return false; 96 return true; 97 } 98 99 unittest { 100 assert(!misspelled("")); 101 assert(!misspelled(null)); 102 } 103 104 /** 105 * Returns two arrays of words that could be used for spelling correction. 106 * The first list is of edit distence of one and the second of edit distance 107 * of two. 108 */ 109 private string[][] candidates(string word) { 110 string[][] candy = new string[][2]; 111 if(!misspelled(word)) 112 return candy; 113 foreach(w, i; model) { 114 if(std.math.abs(cast(long)word.length - cast(long)w.length) > 2) 115 { /* a length difference greater than 2 is more than 2 edits */ } 116 else { 117 auto dist = levenshteinDistance(w, word); 118 if(dist == 1) 119 candy[0] ~= w; 120 if(dist == 2) 121 candy[1] ~= w; 122 } 123 } 124 return candy; 125 } 126 127 unittest { 128 scope(exit) model = null; 129 buildList(["hello","held","hells"]); 130 auto candy = candidates("helo"); 131 assert(candy[0].sort.equal(["hello", "held"].sort)); 132 assert(candy[1].equal(["hells"])); 133 } 134 135 version(spelling_main) { 136 import std.stdio; 137 import std.datetime; 138 139 alias std.regex.split split; 140 141 void main(string[] args) { 142 if(args.length < 2) { 143 std.stdio.writeln("Please pass a word to check"); 144 return; 145 } 146 147 StopWatch sw; 148 sw.start(); 149 buildList(std.file.readText("big.txt").toLower().match(regex(r"\w+", "g")).map!(a => a.hit)()); 150 151 writefln("Build %s msecs", sw.peek().msecs); 152 153 sw.reset(); 154 sw.start(); 155 if(misspelled(args[1])) 156 std.stdio.writeln(giveWord(args[1])); 157 else 158 std.stdio.writeln("Why you little"); 159 160 writefln("Suggest %s msec", sw.peek().msecs); 161 } 162 }