mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-29 19:57:41 +00:00
feat(web): add .codepointLength field for SearchQuotientNode types
Build-bot: skip build:web Skip-bot: skip
This commit is contained in:
parent
cfe72f4aa2
commit
3149eabae3
5 changed files with 37 additions and 2 deletions
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
import { KMWString } from '@keymanapp/web-utils';
|
||||
|
||||
import { SearchNode } from './distance-modeler.js';
|
||||
import { PathResult, SearchQuotientNode, PathInputProperties } from './search-quotient-node.js';
|
||||
|
|
@ -21,6 +22,9 @@ import Transform = LexicalModelTypes.Transform;
|
|||
// The set of search spaces corresponding to the same 'context' for search.
|
||||
// Whenever a wordbreak boundary is crossed, a new instance should be made.
|
||||
export class LegacyQuotientSpur extends SearchQuotientSpur {
|
||||
protected readonly insertLength: number;
|
||||
protected readonly leftDeleteLength: number;
|
||||
|
||||
/**
|
||||
* Constructs a fresh SearchQuotientNode instance for use in predictive-text
|
||||
* correction and suggestion searches.
|
||||
|
|
@ -31,6 +35,12 @@ export class LegacyQuotientSpur extends SearchQuotientSpur {
|
|||
constructor(space: SearchQuotientNode, inputs: Distribution<Transform>, inputSource: PathInputProperties | ProbabilityMass<Transform>) {
|
||||
super(space, inputs, inputSource);
|
||||
this.queueNodes(this.buildEdgesForNodes(space.previousResults.map(r => r.node)));
|
||||
|
||||
// Compute this SearchPath's codepoint length & edge length.
|
||||
const insert = this.inputs?.[0].sample.insert ?? '';
|
||||
this.insertLength = KMWString.length(insert);
|
||||
|
||||
this.leftDeleteLength = this.inputs?.[0].sample.deleteLeft ?? 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,12 @@ export interface SearchQuotientNode {
|
|||
*/
|
||||
readonly inputSequence: Distribution<Transform>[];
|
||||
|
||||
/**
|
||||
* Reports the length in codepoints of corrected text represented by completed
|
||||
* paths from this instance.
|
||||
*/
|
||||
readonly codepointLength: number;
|
||||
|
||||
/**
|
||||
* Determines the best example text representable by this batcher's portion of
|
||||
* the correction-search graph and its paths.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export class SearchQuotientRoot implements SearchQuotientNode {
|
|||
readonly lowestPossibleSingleCost: number = 0;
|
||||
|
||||
readonly inputCount: number = 0;
|
||||
readonly codepointLength: number = 0;
|
||||
readonly correctionsEnabled: boolean = false;
|
||||
|
||||
private hasBeenProcessed: boolean = false;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {
|
|||
readonly spaceId: number;
|
||||
|
||||
readonly inputCount: number;
|
||||
private _codepointLength: number;
|
||||
protected abstract readonly insertLength: number;
|
||||
protected abstract readonly leftDeleteLength: number
|
||||
|
||||
/**
|
||||
* Marks all results that have already been returned from this instance of SearchPath.
|
||||
|
|
@ -102,6 +105,14 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {
|
|||
return parentInputs.concat(localInputs);
|
||||
}
|
||||
|
||||
get codepointLength(): number {
|
||||
if(this._codepointLength === undefined) {
|
||||
this._codepointLength = this.parentNode.codepointLength + this.insertLength - this.leftDeleteLength;
|
||||
}
|
||||
|
||||
return this._codepointLength;
|
||||
}
|
||||
|
||||
public get lastInput(): Distribution<Readonly<Transform>> {
|
||||
// Shallow-copies the array to prevent external modification; the Transforms
|
||||
// are marked Readonly to prevent their modification as well.
|
||||
|
|
@ -113,7 +124,7 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {
|
|||
const bestLocalInput = this.inputs?.reduce((max, curr) => max.p < curr.p ? curr : max) ?? { sample: { insert: '', deleteLeft: 0 }, p: 1};
|
||||
|
||||
return {
|
||||
text: KMWString.substring(bestPrefix.text, 0, KMWString.length(bestPrefix.text) - bestLocalInput.sample.deleteLeft) + bestLocalInput.sample.insert,
|
||||
text: KMWString.substring(bestPrefix.text, 0, (this.parentNode?.codepointLength ?? 0) - bestLocalInput.sample.deleteLeft) + bestLocalInput.sample.insert,
|
||||
p: bestPrefix.p * bestLocalInput.p
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ describe('SearchQuotientSpur', () => {
|
|||
it('initializes from a lexical model', () => {
|
||||
const path = new LegacyQuotientRoot(testModel);
|
||||
assert.equal(path.inputCount, 0);
|
||||
assert.equal(path.codepointLength, 0);
|
||||
assert.isNumber(path.spaceId);
|
||||
assert.deepEqual(path.bestExample, {text: '', p: 1});
|
||||
assert.deepEqual(path.parents, []);
|
||||
|
|
@ -71,6 +72,7 @@ describe('SearchQuotientSpur', () => {
|
|||
const extendedPath = new LegacyQuotientSpur(rootPath, leadEdgeDistribution, leadEdgeDistribution[0]);
|
||||
|
||||
assert.equal(extendedPath.inputCount, 1);
|
||||
assert.equal(extendedPath.codepointLength, 1);
|
||||
assert.isNumber(extendedPath.spaceId);
|
||||
assert.notEqual(extendedPath.spaceId, rootPath.spaceId);
|
||||
assert.deepEqual(extendedPath.bestExample, {text: 't', p: 0.5});
|
||||
|
|
@ -96,7 +98,7 @@ describe('SearchQuotientSpur', () => {
|
|||
assert.deepEqual(rootPath.parents, []);
|
||||
});
|
||||
|
||||
it('may be built from arbitrary prior SearchPath', () => {
|
||||
it('may be built from arbitrary prior SearchQuotientSpur', () => {
|
||||
const rootPath = new LegacyQuotientRoot(testModel);
|
||||
|
||||
const leadEdgeDistribution = [
|
||||
|
|
@ -128,6 +130,7 @@ describe('SearchQuotientSpur', () => {
|
|||
assert.deepEqual(leadEdgeDistribution, inputClone);
|
||||
|
||||
assert.equal(length2Path.inputCount, 2);
|
||||
assert.equal(length2Path.codepointLength, 2);
|
||||
assert.isNumber(length2Path.spaceId);
|
||||
assert.notEqual(length2Path.spaceId, length1Path.spaceId);
|
||||
assert.deepEqual(length2Path.bestExample, {text: 'tr', p: leadEdgeDistribution[0].p * tailEdgeDistribution[0].p});
|
||||
|
|
@ -156,6 +159,7 @@ describe('SearchQuotientSpur', () => {
|
|||
]);
|
||||
|
||||
assert.equal(length1Path.inputCount, 1);
|
||||
assert.equal(length1Path.codepointLength, 1);
|
||||
assert.isNumber(length1Path.spaceId);
|
||||
assert.notEqual(length1Path.spaceId, rootPath.spaceId);
|
||||
assert.deepEqual(length1Path.bestExample, {text: 't', p: 0.5});
|
||||
|
|
@ -210,6 +214,7 @@ describe('SearchQuotientSpur', () => {
|
|||
assert.deepEqual(leadEdgeDistribution, inputClone);
|
||||
|
||||
assert.equal(length2Path.inputCount, 2);
|
||||
assert.equal(length2Path.codepointLength, 3);
|
||||
assert.isNumber(length2Path.spaceId);
|
||||
assert.notEqual(length2Path.spaceId, length1Path.spaceId);
|
||||
assert.deepEqual(length2Path.bestExample, {text: 'tri', p: leadEdgeDistribution[0].p * tailEdgeDistribution[0].p});
|
||||
|
|
@ -238,6 +243,7 @@ describe('SearchQuotientSpur', () => {
|
|||
]);
|
||||
|
||||
assert.equal(length1Path.inputCount, 1);
|
||||
assert.equal(length1Path.codepointLength, 1);
|
||||
assert.isNumber(length1Path.spaceId);
|
||||
assert.notEqual(length1Path.spaceId, rootPath.spaceId);
|
||||
assert.deepEqual(length1Path.bestExample, {text: 't', p: 0.5});
|
||||
|
|
@ -253,6 +259,7 @@ describe('SearchQuotientSpur', () => {
|
|||
|
||||
assert.equal(pathToSplit.inputCount, 4);
|
||||
assert.equal(distributions.length, pathToSplit.inputCount);
|
||||
assert.equal(pathToSplit.codepointLength, 4); // one char per input, no deletions anywhere
|
||||
// Per assertions documented in the setup above.
|
||||
assert.deepEqual(pathToSplit.bestExample, distributions.reduce(
|
||||
(constructing, current) => ({text: constructing.text + current[0].sample.insert, p: constructing.p * current[0].p}),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue