diff --git a/common/predictive-text/worker/model-compositor.ts b/common/predictive-text/worker/model-compositor.ts index c799073bc8..fde4a75ee6 100644 --- a/common/predictive-text/worker/model-compositor.ts +++ b/common/predictive-text/worker/model-compositor.ts @@ -9,10 +9,21 @@ class ModelCompositor { predict(transformDistribution: Transform | Distribution, context: Context): Suggestion[] { let suggestionDistribution: Distribution = []; + // Assumption: Duplicated 'displayAs' properties indicate duplicated Suggestions. + // When true, we can use an associated array to de-duplicate everything. + let suggestionDistribMap: {[key: string]: ProbabilityMass} = {}; + if(!(transformDistribution instanceof Array)) { transformDistribution = [ {sample: transformDistribution, p: 1.0} ]; } + // Find the transform for the actual keypress. + let inputTransform = transformDistribution.sort(function(a, b) { + return b.p - a.p; + })[0].sample; + + let inputResult = []; + for(let alt of transformDistribution) { let transform = alt.sample; let distribution = this.lexicalModel.predict(transform, context); @@ -24,15 +35,24 @@ class ModelCompositor { pair.sample.transformId = transform.id; } - let compositedPair = {sample: pair.sample, p: pair.p * alt.p}; - suggestionDistribution.push(compositedPair); + // Combine duplicate samples. + let s = suggestionDistribMap[pair.sample.displayAs]; + if(s) { + s.p += pair.p * alt.p; + } else { + let compositedPair = {sample: pair.sample, p: pair.p * alt.p}; + //suggestionDistribution.push(compositedPair); + suggestionDistribMap[pair.sample.displayAs] = compositedPair; + } }); } - // Now that we've calculated the set of probability masses, time to join 'em together - // and return the most likely candidates. - - // TODO: What if the model emits duplicate samples, each with their own mass? + // Now that we've calculated a unique set of probability masses, time to make them into a proper + // distribution and prep for return. + for(let key in suggestionDistribMap) { + let pair = suggestionDistribMap[key]; + suggestionDistribution.push(pair); + } suggestionDistribution = suggestionDistribution.sort(function(a, b) { return b.p - a.p; // Use descending order - we want the largest probabilty suggestions first! diff --git a/common/predictive-text/worker/models/common.ts b/common/predictive-text/worker/models/common.ts new file mode 100644 index 0000000000..9383d2c22a --- /dev/null +++ b/common/predictive-text/worker/models/common.ts @@ -0,0 +1,25 @@ +namespace models { + export class Common { + static applyTransform(transform: Transform, context: Context): Context { + // First, get the current context + let fullLeftContext = context.left || ''; + let lLen = fullLeftContext.length; + let lDel = lLen < transform.deleteLeft ? lLen : transform.deleteLeft; + + let leftContext = fullLeftContext.substr(0, lLen - lDel) + (transform.insert || ''); + + let fullRightContext = context.right || ''; + let rLen = fullRightContext.length; + let rDel = rLen < transform.deleteRight ? rLen : transform.deleteRight; + + let rightContext = fullRightContext.substr(rDel); + + return { + left: leftContext, + right: rightContext, + startOfBuffer: context.startOfBuffer, + endOfBuffer: context.endOfBuffer + }; + } + } +} \ No newline at end of file diff --git a/common/predictive-text/worker/models/trie-model.ts b/common/predictive-text/worker/models/trie-model.ts index 95f70e15d9..40da1b4aa2 100644 --- a/common/predictive-text/worker/models/trie-model.ts +++ b/common/predictive-text/worker/models/trie-model.ts @@ -23,6 +23,7 @@ */ /// +/// /** * @file trie-model.ts @@ -101,13 +102,15 @@ }))); } - // EVERYTHING to the left of the cursor: - let fullLeftContext = context.left || ''; - // Stuff to the left of the cursor in the current word. - let leftContext = this.getLastWord(fullLeftContext); + // Compute the results of the keystroke: + let newContext = Common.applyTransform(transform, context); + + // Computes the different in word length after applying the transform above. + let leftDelOffset = transform.deleteLeft - transform.insert.length; + // All text to the left of the cursor INCLUDING anything that has // just been typed. - let prefix = leftContext + (transform.insert || ''); + let prefix = this.getLastWord(newContext.left); // Return suggestions from the trie. return makeDistribution(this._trie.lookup(prefix).map(({text, p}) => ({ @@ -117,7 +120,7 @@ // Delete whatever the prefix that the user wrote. // Note: a separate capitalization/orthography engine can take this // result and transform it as needed. - deleteLeft: leftContext.length, + deleteLeft: leftDelOffset + prefix.length, }, displayAs: text, p: p