@@ -28,9 +28,7 @@ export const QUEUE_NODE_COMPARATOR: Comparator<SearchNode> = function(arg1, arg2
2828// Whenever a wordbreak boundary is crossed, a new instance should be made.
2929export class SearchQuotientSpur implements SearchQuotientNode {
3030 private selectionQueue : PriorityQueue < SearchNode > = new PriorityQueue ( QUEUE_NODE_COMPARATOR ) ;
31- private inputs : Distribution < Transform > ;
32-
33- readonly rootPath : SearchQuotientSpur ;
31+ readonly inputs ?: Distribution < Readonly < Transform > > ;
3432
3533 private parentPath : SearchQuotientSpur ;
3634 readonly spaceId : number ;
@@ -58,45 +56,38 @@ export class SearchQuotientSpur implements SearchQuotientNode {
5856 */
5957 private lowestCostAtDepth : number [ ] ;
6058
61- /**
62- * Clone constructor. Deep-copies its internal queues, but not search nodes.
63- * @param instance
64- */
65- constructor ( instance : SearchQuotientSpur ) ;
6659 /**
6760 * Constructs a fresh SearchSpace instance for used in predictive-text correction
6861 * and suggestion searches.
6962 * @param baseSpaceId
7063 * @param model
7164 */
7265 constructor ( model : LexicalModel ) ;
73- constructor ( arg1 : SearchQuotientSpur | LexicalModel ) {
66+ constructor ( space : SearchQuotientSpur , inputs : Distribution < Transform > , bestProbFromSet : number ) ;
67+ constructor ( arg1 : LexicalModel | SearchQuotientSpur , inputs ?: Distribution < Transform > , bestProbFromSet ?: number ) {
7468 this . spaceId = generateSpaceSeed ( ) ;
7569
7670 if ( arg1 instanceof SearchQuotientSpur ) {
77- const parentSpace = arg1 ;
78- this . lowestCostAtDepth = parentSpace . lowestCostAtDepth . slice ( ) ;
79- this . rootPath = parentSpace . rootPath ;
80- this . parentPath = parentSpace ;
71+ const parentNode = arg1 as SearchQuotientSpur ;
72+ const logTierCost = - Math . log ( bestProbFromSet ) ;
8173
82- return ;
83- }
74+ this . inputs = inputs ;
75+ this . lowestCostAtDepth = parentNode . lowestCostAtDepth . concat ( logTierCost ) ;
76+ this . parentPath = parentNode ;
77+
78+ this . addEdgesForNodes ( parentNode . completedPaths ) ;
8479
85- const model = arg1 ;
86- if ( ! model . traverseFromRoot ) {
87- throw new Error ( "The provided model does not implement the `traverseFromRoot` function, which is needed to support robust correction searching." ) ;
80+ return ;
8881 }
8982
90- const rootNode = new SearchNode ( model . traverseFromRoot ( ) , this . spaceId , model . toKey ? model . toKey . bind ( model ) : null ) ;
91- this . selectionQueue . enqueue ( rootNode ) ;
83+ const model = arg1 as LexicalModel ;
84+ this . selectionQueue . enqueue ( new SearchNode ( model . traverseFromRoot ( ) , this . spaceId , t => model . toKey ( t ) ) ) ;
9285 this . lowestCostAtDepth = [ ] ;
93- this . rootPath = this ;
94-
9586 this . completedPaths = [ ] ;
9687 }
9788
9889 /**
99- * Retrieves the sequence of inputs
90+ * Retrieves the sequences of inputs that led to this SearchPath.
10091 */
10192 public get inputSequence ( ) : Distribution < Transform > [ ] {
10293 if ( this . parentPath ) {
@@ -141,76 +132,33 @@ export class SearchQuotientSpur implements SearchQuotientNode {
141132 return this . parentPath ?. correctionsEnabled || this . inputs ?. length > 1 ;
142133 }
143134
144- /**
145- * Extends the correction-search process embodied by this SearchPath by an extra
146- * input character, according to the characters' likelihood in the distribution.
147- * @param inputDistribution The fat-finger distribution for the incoming keystroke (or
148- * just the raw keystroke if corrections are disabled)
149- */
150- addInput ( inputDistribution : Distribution < Transform > , bestProbFromSet : number ) : SearchQuotientSpur {
151- const input = inputDistribution ;
152-
153- const childSpace = new SearchQuotientSpur ( this ) ;
154-
155- childSpace . inputs = inputDistribution ;
156- const lastDepthCost = this . lowestCostAtDepth [ this . lowestCostAtDepth . length - 1 ] ?? 0 ;
157- const logTierCost = - Math . log ( bestProbFromSet ) ;
158- childSpace . lowestCostAtDepth . push ( lastDepthCost + logTierCost ) ;
159-
160- // With a newly-available input, we can extend new input-dependent paths from
161- // our previously-reached 'extractedResults' nodes.
162- let newlyAvailableEdges : SearchNode [ ] = [ ] ;
163- let batches = this . completedPaths ?. map ( function ( node ) {
164- let deletions = node . buildDeletionEdges ( input , childSpace . spaceId ) ;
165- let substitutions = node . buildSubstitutionEdges ( input , childSpace . spaceId ) ;
166-
167- // Skip the queue for the first pass; there will ALWAYS be at least one pass,
168- // and queue-enqueing does come with a cost. Avoid the unnecessary overhead.
169- return substitutions . flatMap ( e => e . processSubsetEdge ( ) ) . concat ( deletions ) ;
170- } ) ;
171-
172- childSpace . completedPaths = [ ] ;
173- childSpace . returnedValues = { } ;
174-
175- batches ?. forEach ( function ( batch ) {
176- newlyAvailableEdges = newlyAvailableEdges . concat ( batch ) ;
177- } ) ;
178-
179- childSpace . selectionQueue . enqueueAll ( newlyAvailableEdges ) ;
180-
181- return childSpace ;
182- }
183-
184135 public get currentCost ( ) : number {
185136 const parentCost = this . parentPath ?. currentCost ?? Number . POSITIVE_INFINITY ;
186137 const localCost = this . selectionQueue . peek ( ) ?. currentCost ?? Number . POSITIVE_INFINITY ;
187138
188139 return Math . min ( localCost , parentCost ) ;
189140 }
190141
191- /**
192- * Given an incoming SearchNode, this method will build all outgoing edges
193- * from the node that correspond to processing this SearchPath instance's
194- * input distribution.
195- * @param currentNode
196- */
197- private addEdgesForNodes ( currentNode : SearchNode ) {
198- // Hard restriction: no further edits will be supported. This helps keep the search
199- // more narrowly focused.
200- const substitutionsOnly = currentNode . editCount == 2 ;
201-
202- let deletionEdges : SearchNode [ ] = [ ] ;
203- if ( ! substitutionsOnly ) {
204- deletionEdges = currentNode . buildDeletionEdges ( this . inputs , this . spaceId ) ;
205- }
206- const substitutionEdges = currentNode . buildSubstitutionEdges ( this . inputs , this . spaceId ) ;
142+ private addEdgesForNodes ( baseNodes : ReadonlyArray < SearchNode > ) {
143+ // With a newly-available input, we can extend new input-dependent paths from
144+ // our previously-reached 'extractedResults' nodes.
145+ let outboundNodes = baseNodes . map ( ( node ) => {
146+ // Hard restriction: no further edits will be supported. This helps keep the search
147+ // more narrowly focused.
148+ const substitutionsOnly = node . editCount == 2 ;
149+
150+ let deletionEdges : SearchNode [ ] = [ ] ;
151+ if ( ! substitutionsOnly ) {
152+ deletionEdges = node . buildDeletionEdges ( this . inputs , this . spaceId ) ;
153+ }
154+ const substitutionEdges = node . buildSubstitutionEdges ( this . inputs , this . spaceId ) ;
207155
208- // Skip the queue for the first pass; there will ALWAYS be at least one pass,
209- // and queue-enqueing does come with a cost - avoid unnecessary overhead here.
210- let batch = substitutionEdges . flatMap ( e => e . processSubsetEdge ( ) ) . concat ( deletionEdges ) ;
156+ // Skip the queue for the first pass; there will ALWAYS be at least one pass,
157+ // and queue-enqueing does come with a cost - avoid unnecessary overhead here.
158+ return substitutionEdges . flatMap ( e => e . processSubsetEdge ( ) ) . concat ( deletionEdges ) ;
159+ } ) . flat ( ) ;
211160
212- this . selectionQueue . enqueueAll ( batch ) ;
213- // We didn't reach an end-node, so we just end the iteration and continue the search.
161+ this . selectionQueue . enqueueAll ( outboundNodes ) ;
214162 }
215163
216164 /**
@@ -233,7 +181,7 @@ export class SearchQuotientSpur implements SearchQuotientNode {
233181 const result = this . parentPath . handleNextNode ( ) ;
234182
235183 if ( result . type == 'complete' ) {
236- this . addEdgesForNodes ( result . finalNode ) ;
184+ this . addEdgesForNodes ( [ result . finalNode ] ) ;
237185 }
238186
239187 return {
0 commit comments