mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-30 12:17:41 +00:00
Merge branch 'refactor/web/use-interface-as-search-parent' into refactor/web/differentiate-module-types
This commit is contained in:
commit
5d294df1bf
6 changed files with 236 additions and 195 deletions
|
|
@ -13,6 +13,7 @@ import { SearchNode, SearchResult } from "./distance-modeler.js";
|
|||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
import { SearchQuotientSpur } from "./search-quotient-spur.js";
|
||||
|
||||
let SPACE_ID_SEED = 0;
|
||||
|
||||
|
|
@ -63,19 +64,6 @@ export interface SearchQuotientNode {
|
|||
*/
|
||||
handleNextNode(): PathResult;
|
||||
|
||||
/**
|
||||
* Denotes whether or not the represented search space includes paths built from
|
||||
* the specified set of keystroke input distributions. The distribution count
|
||||
* should match .inputCount - no omissions or extras are permitted.
|
||||
*
|
||||
* Designed explicitly for use in unit testing; it's not super-efficient, so
|
||||
* avoid live use.
|
||||
*
|
||||
* @param keystrokeDistributions
|
||||
* @internal
|
||||
*/
|
||||
hasInputs(keystrokeDistributions: Distribution<Transform>[]): boolean;
|
||||
|
||||
/**
|
||||
* Increases the editing range that will be considered for determining
|
||||
* correction distances.
|
||||
|
|
@ -133,4 +121,73 @@ export interface SearchQuotientNode {
|
|||
* the correction-search graph and its paths.
|
||||
*/
|
||||
readonly bestExample: { text: string, p: number };
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes whether or not the represented search-space quotient path includes
|
||||
* paths built from the specified set of keystroke input distributions. The
|
||||
* distribution count should match .inputCount - no omissions or extras are
|
||||
* permitted.
|
||||
*
|
||||
* Designed explicitly for use in unit testing; it's not super-efficient, so
|
||||
* avoid live use.
|
||||
*
|
||||
* @param keystrokeDistributions
|
||||
* @internal
|
||||
*/
|
||||
export function quotientPathHasInputs(node: SearchQuotientNode, keystrokeDistributions: Distribution<Transform>[]): boolean {
|
||||
if(!(node instanceof SearchQuotientSpur)) {
|
||||
for(const p of node.parents) {
|
||||
if(quotientPathHasInputs(p, keystrokeDistributions)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(node.inputCount == 0) {
|
||||
return keystrokeDistributions.length == 0;
|
||||
} else if(keystrokeDistributions.length != node.inputCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tailInput = [...keystrokeDistributions[keystrokeDistributions.length - 1]];
|
||||
keystrokeDistributions = keystrokeDistributions.slice(0, keystrokeDistributions.length - 1);
|
||||
const localInput = node.lastInput;
|
||||
|
||||
const parentHasInput = () => !!node.parents.find(p => quotientPathHasInputs(p, keystrokeDistributions));
|
||||
|
||||
// Actual reference match? Easy mode.
|
||||
if(localInput == tailInput) {
|
||||
return parentHasInput();
|
||||
} else if(localInput.length != tailInput.length) {
|
||||
return false;
|
||||
} else {
|
||||
for(let entry of tailInput) {
|
||||
const matchIndex = localInput.findIndex((x) => {
|
||||
const s1 = x.sample;
|
||||
const s2 = entry.sample;
|
||||
// Check for equal reference first before the other checks; it makes a nice shortcut.
|
||||
if(x == entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(x.p == entry.p && s1.deleteLeft == s2.deleteLeft
|
||||
&& s1.id == s2.id && ((s1.deleteRight ?? 0) == (s2.deleteRight ?? 0)) && s1.insert == s2.insert
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if(matchIndex == -1) {
|
||||
return false;
|
||||
} else {
|
||||
tailInput.splice(matchIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return parentHasInput();
|
||||
}
|
||||
}
|
||||
|
|
@ -70,51 +70,6 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {
|
|||
return parentInputs.concat(localInputs);
|
||||
}
|
||||
|
||||
public hasInputs(keystrokeDistributions: Distribution<Transform>[]): boolean {
|
||||
if(this.inputCount == 0) {
|
||||
return keystrokeDistributions.length == 0;
|
||||
} else if(keystrokeDistributions.length != this.inputCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tailInput = [...keystrokeDistributions[keystrokeDistributions.length - 1]];
|
||||
keystrokeDistributions = keystrokeDistributions.slice(0, keystrokeDistributions.length - 1);
|
||||
const localInput = this.lastInput;
|
||||
|
||||
const parentHasInput = () => !!this.parents.find(p => p.hasInputs(keystrokeDistributions));
|
||||
|
||||
// Actual reference match? Easy mode.
|
||||
if(localInput == tailInput) {
|
||||
return parentHasInput();
|
||||
} else if(localInput.length != tailInput.length) {
|
||||
return false;
|
||||
} else {
|
||||
for(let entry of tailInput) {
|
||||
const matchIndex = localInput.findIndex((x) => {
|
||||
const s1 = x.sample;
|
||||
const s2 = entry.sample;
|
||||
// Check for equal reference first before the other checks; it makes a nice shortcut.
|
||||
if(x == entry) {
|
||||
return true;
|
||||
} if(x.p == entry.p && s1.deleteLeft == s2.deleteLeft
|
||||
&& s1.id == s2.id && ((s1.deleteRight ?? 0) == (s2.deleteRight ?? 0)) && s1.insert == s2.insert
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if(matchIndex == -1) {
|
||||
return false;
|
||||
} else {
|
||||
tailInput.splice(matchIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return parentHasInput();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export * from './correction/context-tokenization.js';
|
|||
export { ContextTracker } from './correction/context-tracker.js';
|
||||
export { ContextTransition } from './correction/context-transition.js';
|
||||
export * from './correction/distance-modeler.js';
|
||||
export * from './correction/search-quotient-node.js';
|
||||
export * from './correction/search-quotient-spur.js';
|
||||
export * from './correction/search-quotient-node.js';
|
||||
export * from './correction/legacy-quotient-root.js';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { default as defaultBreaker } from '@keymanapp/models-wordbreakers';
|
|||
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
import { ContextToken, correction, getBestMatches, models, preprocessInputSources, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index';
|
||||
import { ContextToken, correction, getBestMatches, models, preprocessInputSources, quotientPathHasInputs, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import ExecutionTimer = correction.ExecutionTimer;
|
||||
|
|
@ -71,7 +71,8 @@ describe('ContextToken', function() {
|
|||
assert.equal(token.exampleInput, 'and');
|
||||
|
||||
assert.equal(token.searchModule.inputCount, 3);
|
||||
assert.isTrue(token.searchModule.hasInputs([
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
token.searchModule, [
|
||||
[{sample: { insert: 'a', deleteLeft: 0 }, p: 1}],
|
||||
[{sample: { insert: 'n', deleteLeft: 0 }, p: 1}],
|
||||
[{sample: { insert: 'd', deleteLeft: 0 }, p: 1}]
|
||||
|
|
@ -110,7 +111,8 @@ describe('ContextToken', function() {
|
|||
token2.inputRange.forEach((entry) => assert.isTrue(merged.inputRange.indexOf(entry) > -1));
|
||||
token3.inputRange.forEach((entry) => assert.isTrue(merged.inputRange.indexOf(entry) > -1));
|
||||
|
||||
assert.isTrue(merged.searchModule.hasInputs([
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
merged.searchModule, [
|
||||
[{sample: { insert: 'c', deleteLeft: 0 }, p: 1}],
|
||||
[{sample: { insert: 'a', deleteLeft: 0 }, p: 1}],
|
||||
[{sample: { insert: 'n', deleteLeft: 0 }, p: 1}],
|
||||
|
|
@ -207,7 +209,8 @@ describe('ContextToken', function() {
|
|||
const merged = ContextToken.merge(tokensToMerge, plainModel);
|
||||
assert.equal(merged.exampleInput, "applesandsourgrapes");
|
||||
assert.deepEqual(merged.inputRange, srcTransforms.map((t) => ({ trueTransform: t, inputStartIndex: 0, bestProbFromSet: 1 }) ));
|
||||
assert.isTrue(merged.searchModule.hasInputs(
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
merged.searchModule,
|
||||
srcTransforms.map((t) => ([{sample: t, p: 1}]))
|
||||
));
|
||||
});
|
||||
|
|
@ -268,7 +271,8 @@ describe('ContextToken', function() {
|
|||
const merged = ContextToken.merge(tokensToMerge, plainModel);
|
||||
assert.equal(merged.exampleInput, toMathematicalSMP("applesandsourgrapes"));
|
||||
assert.deepEqual(merged.inputRange, srcTransforms.map((t) => ({ trueTransform: t, inputStartIndex: 0, bestProbFromSet: 1 }) ));
|
||||
assert.isTrue(merged.searchModule.hasInputs(
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
merged.searchModule,
|
||||
srcTransforms.map((t) => ([{sample: t, p: 1}]))
|
||||
));
|
||||
});
|
||||
|
|
@ -302,7 +306,7 @@ describe('ContextToken', function() {
|
|||
};
|
||||
|
||||
assert.equal(tokenToSplit.sourceText, 'can\'');
|
||||
tokenToSplit.searchModule.hasInputs(keystrokeDistributions);
|
||||
assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions));
|
||||
|
||||
// And now for the "fun" part.
|
||||
const resultsOfSplit = tokenToSplit.split({
|
||||
|
|
@ -319,8 +323,8 @@ describe('ContextToken', function() {
|
|||
|
||||
assert.equal(resultsOfSplit.length, 2);
|
||||
assert.sameOrderedMembers(resultsOfSplit.map(t => t.exampleInput), ['can', '\'']);
|
||||
assert.isTrue(resultsOfSplit[0].searchModule.hasInputs(keystrokeDistributions.slice(0, 3)));
|
||||
assert.isTrue(resultsOfSplit[1].searchModule.hasInputs([keystrokeDistributions[3]]));
|
||||
assert.isTrue(quotientPathHasInputs(resultsOfSplit[0].searchModule, keystrokeDistributions.slice(0, 3)));
|
||||
assert.isTrue(quotientPathHasInputs(resultsOfSplit[1].searchModule, [keystrokeDistributions[3]]));
|
||||
});
|
||||
|
||||
it("handles mid-transform splits correctly", () => {
|
||||
|
|
@ -338,7 +342,7 @@ describe('ContextToken', function() {
|
|||
};
|
||||
|
||||
assert.equal(tokenToSplit.sourceText, 'biglargetransform');
|
||||
assert.isTrue(tokenToSplit.searchModule.hasInputs(keystrokeDistributions));
|
||||
assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions));
|
||||
|
||||
// And now for the "fun" part.
|
||||
const resultsOfSplit = tokenToSplit.split({
|
||||
|
|
@ -367,7 +371,8 @@ describe('ContextToken', function() {
|
|||
})));
|
||||
|
||||
for(let i = 0; i < resultsOfSplit.length; i++) {
|
||||
assert.isTrue(resultsOfSplit[i].searchModule.hasInputs([
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[i].searchModule, [
|
||||
[{sample: { insert: splitTextArray[i], deleteLeft: 0, deleteRight: 0 }, p: 1}]
|
||||
]));
|
||||
}
|
||||
|
|
@ -392,7 +397,7 @@ describe('ContextToken', function() {
|
|||
};
|
||||
|
||||
assert.equal(tokenToSplit.exampleInput, 'largelongtransforms');
|
||||
tokenToSplit.searchModule.hasInputs(keystrokeDistributions);
|
||||
assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions));
|
||||
|
||||
// And now for the "fun" part.
|
||||
const resultsOfSplit = tokenToSplit.split({
|
||||
|
|
@ -422,49 +427,55 @@ describe('ContextToken', function() {
|
|||
{ trueTransform: keystrokeDistributions[2][0].sample, inputStartIndex: 'ng'.length, bestProbFromSet: 1 }
|
||||
]);
|
||||
|
||||
assert.isTrue(resultsOfSplit[0].searchModule.hasInputs([
|
||||
keystrokeDistributions[0],
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice(0, 4) // gets the 'arge' portion & the deleteLefts.
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]));
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[0].searchModule,[
|
||||
keystrokeDistributions[0],
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice(0, 4) // gets the 'arge' portion & the deleteLefts.
|
||||
}, p: entry.p
|
||||
}
|
||||
})
|
||||
]
|
||||
));
|
||||
|
||||
assert.isTrue(resultsOfSplit[1].searchModule.hasInputs([
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice('arge'.length),
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice(0, 'ng'.length), // gets the 'ng' portion.
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]));
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[1].searchModule, [
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice('arge'.length),
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice(0, 'ng'.length), // gets the 'ng' portion.
|
||||
}, p: entry.p
|
||||
}
|
||||
})
|
||||
]
|
||||
));
|
||||
|
||||
assert.isTrue(resultsOfSplit[2].searchModule.hasInputs([
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice('ng'.length), // drops the 'ng' portion.
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]));
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[2].searchModule, [
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: entry.sample.insert.slice('ng'.length), // drops the 'ng' portion.
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]
|
||||
));
|
||||
});
|
||||
|
||||
it("handles messy mid-transform splits correctly - non-BMP text", () => {
|
||||
|
|
@ -486,7 +497,7 @@ describe('ContextToken', function() {
|
|||
};
|
||||
|
||||
assert.equal(tokenToSplit.exampleInput, toMathematicalSMP('largelongtransforms'));
|
||||
tokenToSplit.searchModule.hasInputs(keystrokeDistributions);
|
||||
assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions));
|
||||
|
||||
// And now for the "fun" part.
|
||||
const resultsOfSplit = tokenToSplit.split({
|
||||
|
|
@ -516,49 +527,55 @@ describe('ContextToken', function() {
|
|||
{ trueTransform: keystrokeDistributions[2][0].sample, inputStartIndex: 'ng'.length, bestProbFromSet: 1 }
|
||||
]);
|
||||
|
||||
assert.isTrue(resultsOfSplit[0].searchModule.hasInputs([
|
||||
keystrokeDistributions[0],
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 0, 4) // gets the 'arge' portion & the deleteLefts.
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]));
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[0].searchModule, [
|
||||
keystrokeDistributions[0],
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 0, 4) // gets the 'arge' portion & the deleteLefts.
|
||||
}, p: entry.p
|
||||
}
|
||||
})
|
||||
]
|
||||
));
|
||||
|
||||
assert.isTrue(resultsOfSplit[1].searchModule.hasInputs([
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 'arge'.length),
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 0, 'ng'.length), // gets the 'ng' portion.
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]));
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[1].searchModule, [
|
||||
keystrokeDistributions[1].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 'arge'.length),
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 0, 'ng'.length), // gets the 'ng' portion.
|
||||
}, p: entry.p
|
||||
}
|
||||
})
|
||||
]
|
||||
));
|
||||
|
||||
assert.isTrue(resultsOfSplit[2].searchModule.hasInputs([
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 'ng'.length), // drops the 'ng' portion.
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
}),
|
||||
]));
|
||||
assert.isTrue(quotientPathHasInputs(
|
||||
resultsOfSplit[2].searchModule, [
|
||||
keystrokeDistributions[2].map((entry) => {
|
||||
return {
|
||||
sample: {
|
||||
...entry.sample,
|
||||
insert: KMWString.substring(entry.sample.insert, 'ng'.length), // drops the 'ng' portion.
|
||||
deleteLeft: 0
|
||||
}, p: entry.p
|
||||
}
|
||||
})
|
||||
]
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
import { LegacyQuotientRoot, models, quotientPathHasInputs } from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import { buildSimplePathSplitFixture } from './search-quotient-spur.tests.js';
|
||||
|
||||
import TrieModel = models.TrieModel;
|
||||
|
||||
const testModel = new TrieModel(jsonFixture('models/tries/english-1000'));
|
||||
|
||||
describe('quotientNodeHasParents()', () => {
|
||||
it('matches an empty array on root SearchPaths', () => {
|
||||
assert.isTrue(quotientPathHasInputs(new LegacyQuotientRoot(testModel), []));
|
||||
});
|
||||
|
||||
it('matches all path inputs when provided in proper order', () => {
|
||||
const { paths, distributions } = buildSimplePathSplitFixture();
|
||||
assert.isTrue(quotientPathHasInputs(paths[4], distributions));
|
||||
});
|
||||
|
||||
it('does not match when any path input component is missing', () => {
|
||||
const { paths, distributions } = buildSimplePathSplitFixture();
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(1)));
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(2)));
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(3)));
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(0, 3)));
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(0, 1).concat(distributions.slice(2))));
|
||||
});
|
||||
|
||||
it('does not match when path inputs are not in proper order', () => {
|
||||
const { paths, distributions } = buildSimplePathSplitFixture();
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice().reverse()));
|
||||
|
||||
// Random shuffle.
|
||||
let shuffled: typeof distributions;
|
||||
let isShuffled: boolean;
|
||||
do {
|
||||
shuffled = distributions.slice().sort(() => Math.random() * 2 - 1);
|
||||
// Validate that we actually shuffled - that we didn't land on the original order!
|
||||
isShuffled = false;
|
||||
for(let i = 0; i < distributions.length; i++) {
|
||||
if(distributions[i] != shuffled[i]) {
|
||||
isShuffled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(!isShuffled);
|
||||
assert.isFalse(quotientPathHasInputs(paths[4], shuffled));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
import { LegacyQuotientSpur, models, LegacyQuotientRoot } from '@keymanapp/lm-worker/test-index';
|
||||
import { LegacyQuotientSpur, models, LegacyQuotientRoot, quotientPathHasInputs } from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import TrieModel = models.TrieModel;
|
||||
|
||||
|
|
@ -193,48 +193,7 @@ describe('SearchQuotientSpur', () => {
|
|||
(constructing, current) => ({text: constructing.text + current[0].sample.insert, p: constructing.p * current[0].p}),
|
||||
{text: '', p: 1})
|
||||
);
|
||||
assert.isTrue(pathToSplit.hasInputs(distributions));
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasInputs()', () => {
|
||||
it('matches an empty array on root SearchPaths', () => {
|
||||
assert.isTrue(new LegacyQuotientRoot(testModel).hasInputs([]));
|
||||
});
|
||||
|
||||
it('matches all path inputs when provided in proper order', () => {
|
||||
const { paths, distributions } = buildSimplePathSplitFixture();
|
||||
assert.isTrue(paths[4].hasInputs(distributions));
|
||||
});
|
||||
|
||||
it('does not match when any path input component is missing', () => {
|
||||
const { paths, distributions } = buildSimplePathSplitFixture();
|
||||
assert.isFalse(paths[4].hasInputs(distributions.slice(1)));
|
||||
assert.isFalse(paths[4].hasInputs(distributions.slice(2)));
|
||||
assert.isFalse(paths[4].hasInputs(distributions.slice(3)));
|
||||
assert.isFalse(paths[4].hasInputs(distributions.slice(0, 3)));
|
||||
assert.isFalse(paths[4].hasInputs(distributions.slice(0, 1).concat(distributions.slice(2))));
|
||||
});
|
||||
|
||||
it('does not match when path inputs are not in proper order', () => {
|
||||
const { paths, distributions } = buildSimplePathSplitFixture();
|
||||
assert.isFalse(paths[4].hasInputs(distributions.slice().reverse()));
|
||||
|
||||
// Random shuffle.
|
||||
let shuffled: typeof distributions;
|
||||
let isShuffled: boolean;
|
||||
do {
|
||||
shuffled = distributions.slice().sort(() => Math.random() * 2 - 1);
|
||||
// Validate that we actually shuffled - that we didn't land on the original order!
|
||||
isShuffled = false;
|
||||
for(let i = 0; i < distributions.length; i++) {
|
||||
if(distributions[i] != shuffled[i]) {
|
||||
isShuffled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(!isShuffled);
|
||||
assert.isFalse(paths[4].hasInputs(shuffled));
|
||||
assert.isTrue(quotientPathHasInputs(pathToSplit, distributions));
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue