mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-29 19:57:41 +00:00
Merge branch 'change/web/prepare-for-divergent-splits' into feat/web/cluster-splitting-and-merging
This commit is contained in:
commit
71fcb02120
5 changed files with 28 additions and 26 deletions
|
|
@ -3,11 +3,12 @@
|
|||
*
|
||||
* Created by jahorton on 2025-10-20
|
||||
*
|
||||
* This file defines the predictive-text engine's SearchSpace class, which is used to
|
||||
* manage the search-space(s) for text corrections within the engine.
|
||||
* This file defines the predictive-text engine's SearchQuotientCluster class,
|
||||
* which is used to manage the search-space(s) for text corrections within the
|
||||
* engine.
|
||||
*/
|
||||
|
||||
import { QueueComparator as Comparator, PriorityQueue } from '@keymanapp/web-utils';
|
||||
import { QueueComparator, PriorityQueue } from '@keymanapp/web-utils';
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
import { SearchNode, SearchResult } from './distance-modeler.js';
|
||||
|
|
@ -15,17 +16,13 @@ import { LegacyQuotientRoot } from './legacy-quotient-root.js';
|
|||
import { generateSpaceSeed, InputSegment, PathResult, SearchQuotientNode } from './search-quotient-node.js';
|
||||
import { SearchQuotientSpur } from './search-quotient-spur.js';
|
||||
|
||||
const PATH_QUEUE_COMPARATOR: Comparator<SearchQuotientNode> = (a, b) => {
|
||||
const PATH_QUEUE_COMPARATOR: QueueComparator<SearchQuotientNode> = (a, b) => {
|
||||
return a.currentCost - b.currentCost;
|
||||
}
|
||||
|
||||
// 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 SearchQuotientCluster implements SearchQuotientNode {
|
||||
// While most functions can be done directly from SearchSpace, merging and
|
||||
// splitting will need access to SearchQuotientSpur-specific members. It's
|
||||
// also cleaner to not allow nested SearchQuotientClusters while we haven't
|
||||
// worked out support for such a scenario.
|
||||
private selectionQueue: PriorityQueue<SearchQuotientNode> = new PriorityQueue(PATH_QUEUE_COMPARATOR);
|
||||
readonly spaceId: number;
|
||||
|
||||
|
|
@ -51,16 +48,15 @@ export class SearchQuotientCluster implements SearchQuotientNode {
|
|||
private _processedEdgeSet?: {[pathKey: string]: boolean} = {};
|
||||
|
||||
/**
|
||||
* Provides a heuristic for the base cost at each depth if the best
|
||||
* individual input were taken at that level.
|
||||
* Provides a heuristic for the base cost at each depth if the best individual
|
||||
* input were taken at that level.
|
||||
*/
|
||||
readonly lowestPossibleSingleCost: number;
|
||||
|
||||
/**
|
||||
* Constructs a fresh SearchSpace instance for used in predictive-text correction
|
||||
* and suggestion searches.
|
||||
* @param baseSpaceId
|
||||
* @param model
|
||||
* Constructs a fresh SearchQuotientCluster instance for use in
|
||||
* predictive-text correction and suggestion searches.
|
||||
* @param inboundPaths
|
||||
*/
|
||||
constructor(inboundPaths: SearchQuotientNode[]) {
|
||||
if(inboundPaths.length == 0) {
|
||||
|
|
@ -124,7 +120,7 @@ export class SearchQuotientCluster implements SearchQuotientNode {
|
|||
* has fat-finger data available, which itself indicates that the user has
|
||||
* corrections enabled.
|
||||
*/
|
||||
get correctionsEnabled(): boolean {
|
||||
get correctionsEnabled(): boolean {
|
||||
const paths = this.selectionQueue.toArray();
|
||||
// When corrections are disabled, the Web engine will only provide individual Transforms
|
||||
// for an input, not a distribution. No distributions means we shouldn't do corrections.
|
||||
|
|
@ -194,7 +190,7 @@ export class SearchQuotientCluster implements SearchQuotientNode {
|
|||
const spaceHeadInputSource = space.inputSegments[0];
|
||||
|
||||
const isOnSplitInput =
|
||||
thisTailSpaceIds.find((entry) => entry == space.inputSource.subsetId)
|
||||
thisTailSpaceIds.some((entry) => entry == space.inputSource.subsetId)
|
||||
&& thisTailInputSource.end == spaceHeadInputSource.start;
|
||||
|
||||
// In this case, we only rebuild the single path; an outer stack frame will reconstitute
|
||||
|
|
@ -210,7 +206,7 @@ export class SearchQuotientCluster implements SearchQuotientNode {
|
|||
// a prior split; if we'd split, it'd be a SearchQuotientCluster on both
|
||||
// ends.
|
||||
if(space instanceof SearchQuotientSpur) {
|
||||
const parentMerge = this.merge(space.parents[0]) as SearchQuotientSpur;
|
||||
const parentMerge = this.merge(space.parents[0]);
|
||||
return space.construct(parentMerge, space.inputs, space.inputSource);
|
||||
}
|
||||
|
||||
|
|
@ -299,9 +295,15 @@ export class SearchQuotientCluster implements SearchQuotientNode {
|
|||
return false;
|
||||
}
|
||||
|
||||
// We need to check if the parents match. Done naively in the manner below, this is O(N^2).
|
||||
// Granted, we shouldn't have _that_ many incoming paths.
|
||||
if(this.parents.find((path) => !space.parents.find((path2) => path.isSameNode(path2)))) {
|
||||
// We need to check if the parents match.
|
||||
// First, is the parent count the same?
|
||||
if(this.parents.length != space.parents.length) {
|
||||
return false;
|
||||
} else if (this.parents.find((path) => !space.parents.find((path2) => path.isSameNode(path2)))) {
|
||||
// Done naively in the manner above, checking each pair of nodes, to
|
||||
// ensure a match is found for each, is O(N^2).
|
||||
//
|
||||
// Granted, we shouldn't have _that_ many incoming paths.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* engine.
|
||||
*/
|
||||
|
||||
import { QueueComparator as Comparator, KMWString, PriorityQueue } from '@keymanapp/web-utils';
|
||||
import { QueueComparator, KMWString, PriorityQueue } from '@keymanapp/web-utils';
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
import { buildMergedTransform } from '@keymanapp/models-templates';
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ import LexicalModel = LexicalModelTypes.LexicalModel;
|
|||
import ProbabilityMass = LexicalModelTypes.ProbabilityMass;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
|
||||
export const QUEUE_NODE_COMPARATOR: Comparator<SearchNode> = function(arg1, arg2) {
|
||||
export const QUEUE_NODE_COMPARATOR: QueueComparator<SearchNode> = function(arg1, arg2) {
|
||||
return arg1.currentCost - arg2.currentCost;
|
||||
}
|
||||
|
||||
|
|
@ -497,7 +497,7 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {
|
|||
}
|
||||
|
||||
isSameNode(space: SearchQuotientNode): boolean {
|
||||
// Easiest cases: when the instances or their ' `spaceId` matches, we have
|
||||
// Easiest cases: when the instances or their `spaceId` matches, we have
|
||||
// a perfect match.
|
||||
if(this == space || this.spaceId == space.spaceId) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ describe('ContextState', () => {
|
|||
assert.equal(state.tokenization.tokens[state.tokenization.tokens.length - 1].searchModule.inputCount, 1);
|
||||
});
|
||||
|
||||
it.skip('handles case where tail token is split into three rather than two', function() {
|
||||
it('handles case where tail token is split into three rather than two', function() {
|
||||
let baseContext = models.tokenize(defaultBreaker, {
|
||||
left: "text'", startOfBuffer: true, endOfBuffer: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ describe('ContextTokenization', function() {
|
|||
}
|
||||
});
|
||||
|
||||
it.skip('handles case that triggers a token merge: can+\'+t', () => {
|
||||
it('handles case that triggers a token merge: can+\'+t', () => {
|
||||
const baseTokens = ['an', ' ', 'apple', ' ', 'a', ' ', 'day', ' ', 'can', '\''];
|
||||
const baseTokenization = new ContextTokenization(baseTokens.map(t => toToken(t)));
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Created by jahorton on 2025-10-29
|
||||
*
|
||||
* This file defines tests for the SearchSpace class of the
|
||||
* This file defines tests for the SearchQuotientCluster class of the
|
||||
* predictive-text correction-search engine.
|
||||
*/
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue