Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ 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;
public readonly insertLength: number;
public readonly leftDeleteLength: number;

/**
* Constructs a fresh SearchQuotientNode instance for use in predictive-text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { QueueComparator as Comparator, PriorityQueue } from '@keymanapp/web-uti
import { LexicalModelTypes } from '@keymanapp/common-types';

import { SearchNode, SearchResult } from './distance-modeler.js';
import { LegacyQuotientRoot } from './legacy-quotient-root.js';
import { generateSpaceSeed, InputSegment, PathResult, SearchQuotientNode } from './search-quotient-node.js';
import { SearchQuotientSpur } from './search-quotient-spur.js';

Expand Down Expand Up @@ -227,7 +228,63 @@ export class SearchQuotientCluster implements SearchQuotientNode {
}

split(charIndex: number): [SearchQuotientNode, SearchQuotientNode][] {
throw new Error('Method not implemented.');
// Don't rebuild if this is already a perfect split point!
if(this.codepointLength <= charIndex) {
return [[this, new LegacyQuotientRoot(this.model)]];
}

const results = this.parents.flatMap((p) => p.split(charIndex));

// Path-deduplication: it is possible for paths to diverge after a point
// and then reconverge at later points. If the split happens before such
// a divergence-reconvergence sequence, it is possible for left-hand side
// entries to be duplicated.
//
// Deduplicate clusters based solely on spaceId, though; an intact cluster
// should match on this basis alone.
const headClusterResultMap: Map<number, {
head: SearchQuotientCluster,
tails: SearchQuotientNode[]
}> = new Map();

const headPathResultMap = new Map<string, {
heads: SearchQuotientNode[],
tails: SearchQuotientNode[]
}>();

results.forEach((result) => {
const [head, tail] = result;

if(head instanceof SearchQuotientCluster) {
const bucket = headClusterResultMap.get(head.spaceId) ?? { head, tails: [] };
bucket.tails.push(tail);
headClusterResultMap.set(head.spaceId, bucket);
return;
}

let key = head.inputCount + '+' + (!(head instanceof SearchQuotientSpur) ? '' : head.splitClusteringKey);
const outerEntry = headPathResultMap.get(key) ?? { heads: [], tails: [] };

if(!outerEntry.heads.find(p => head.isSameNode(p))) {
outerEntry.heads.push(head as SearchQuotientSpur);
}

outerEntry.tails.push(tail);
headPathResultMap.set(key, outerEntry);
});

const resultsFromClusterHeadPaths = [...headClusterResultMap.values()].map((entry) => {
const tailSpace = entry.tails.length > 1 ? new SearchQuotientCluster(entry.tails) : entry.tails[0];
return [entry.head, tailSpace] as [SearchQuotientNode, SearchQuotientNode];
});

const resultsFromHeadPaths = [...headPathResultMap.values()].map((entry) => {
const headSpace = entry.heads.length > 1 ? new SearchQuotientCluster(entry.heads) : entry.heads[0];
const tailSpace = entry.tails.length > 1 ? new SearchQuotientCluster(entry.tails) : entry.tails[0];
return [headSpace, tailSpace] as [SearchQuotientNode, SearchQuotientNode];
});

return resultsFromClusterHeadPaths.concat(resultsFromHeadPaths);
}

isSameNode(space: SearchQuotientNode): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {

readonly inputCount: number;
readonly codepointLength: number;
protected abstract readonly insertLength: number;
protected abstract readonly leftDeleteLength: number
public abstract readonly insertLength: number;
public abstract readonly leftDeleteLength: number

/**
* Marks all results that have already been returned from this instance of SearchPath.
Expand Down Expand Up @@ -517,4 +517,14 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode {
// Finally, we recursively verify that the parent matches.
return this.parentNode.isSameNode(space.parentNode);
}

// Used to identify cluster-compatible components of SearchPaths during SearchCluster split operations.
get splitClusteringKey(): string {
const pathSrc = this.inputSource;
if(!pathSrc) {
return '';
}

return `${pathSrc.segment.start}${pathSrc.segment.end == undefined ? '' : `-${pathSrc.segment.end}`}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ export function precomputationSubsetKeyer(tokenizationEdits: TokenizationTransit
boundaryTextLen = 0;
}

// Note: if any sort of transform tokenization occurs, it's implicitly a split transform.
// Extra inserts after the boundary-insert component are parts split off from the head.
if(boundaryTextLen) {
// transform.deleteLeft was already handled during boundary computation -
// do not include it here!
Expand Down
Loading