1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "suggest/core/suggest.h" 18 19 #include "char_utils.h" 20 #include "dictionary.h" 21 #include "digraph_utils.h" 22 #include "proximity_info.h" 23 #include "suggest/core/dicnode/dic_node.h" 24 #include "suggest/core/dicnode/dic_node_priority_queue.h" 25 #include "suggest/core/dicnode/dic_node_vector.h" 26 #include "suggest/core/dictionary/shortcut_utils.h" 27 #include "suggest/core/policy/scoring.h" 28 #include "suggest/core/policy/traversal.h" 29 #include "suggest/core/policy/weighting.h" 30 #include "suggest/core/session/dic_traverse_session.h" 31 #include "terminal_attributes.h" 32 33 namespace latinime { 34 35 // Initialization of class constants. 36 const int Suggest::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16; 37 const int Suggest::MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE = 2; 38 const float Suggest::AUTOCORRECT_CLASSIFICATION_THRESHOLD = 0.33f; 39 40 /** 41 * Returns a set of suggestions for the given input touch points. The commitPoint argument indicates 42 * whether to prematurely commit the suggested words up to the given point for sentence-level 43 * suggestion. 44 * 45 * Note: Currently does not support concurrent calls across threads. Continuous suggestion is 46 * automatically activated for sequential calls that share the same starting input. 47 * TODO: Stop detecting continuous suggestion. Start using traverseSession instead. 48 */ 49 int Suggest::getSuggestions(ProximityInfo *pInfo, void *traverseSession, 50 int *inputXs, int *inputYs, int *times, int *pointerIds, int *inputCodePoints, 51 int inputSize, int commitPoint, int *outWords, int *frequencies, int *outputIndices, 52 int *outputTypes) const { 53 PROF_OPEN; 54 PROF_START(0); 55 const float maxSpatialDistance = TRAVERSAL->getMaxSpatialDistance(); 56 DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession); 57 tSession->setupForGetSuggestions(pInfo, inputCodePoints, inputSize, inputXs, inputYs, times, 58 pointerIds, maxSpatialDistance, TRAVERSAL->getMaxPointerCount()); 59 // TODO: Add the way to evaluate cache 60 61 initializeSearch(tSession, commitPoint); 62 PROF_END(0); 63 PROF_START(1); 64 65 // keep expanding search dicNodes until all have terminated. 66 while (tSession->getDicTraverseCache()->activeSize() > 0) { 67 expandCurrentDicNodes(tSession); 68 tSession->getDicTraverseCache()->advanceActiveDicNodes(); 69 tSession->getDicTraverseCache()->advanceInputIndex(inputSize); 70 } 71 PROF_END(1); 72 PROF_START(2); 73 const int size = outputSuggestions(tSession, frequencies, outWords, outputIndices, outputTypes); 74 PROF_END(2); 75 PROF_CLOSE; 76 return size; 77 } 78 79 /** 80 * Initializes the search at the root of the lexicon trie. Note that when possible the search will 81 * continue suggestion from where it left off during the last call. 82 */ 83 void Suggest::initializeSearch(DicTraverseSession *traverseSession, int commitPoint) const { 84 if (!traverseSession->getProximityInfoState(0)->isUsed()) { 85 return; 86 } 87 if (TRAVERSAL->allowPartialCommit()) { 88 commitPoint = 0; 89 } 90 91 if (traverseSession->getInputSize() > MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE 92 && traverseSession->isContinuousSuggestionPossible()) { 93 if (commitPoint == 0) { 94 // Continue suggestion 95 traverseSession->getDicTraverseCache()->continueSearch(); 96 } else { 97 // Continue suggestion after partial commit. 98 DicNode *topDicNode = 99 traverseSession->getDicTraverseCache()->setCommitPoint(commitPoint); 100 traverseSession->setPrevWordPos(topDicNode->getPrevWordNodePos()); 101 traverseSession->getDicTraverseCache()->continueSearch(); 102 traverseSession->setPartiallyCommited(); 103 } 104 } else { 105 // Restart recognition at the root. 106 traverseSession->resetCache(TRAVERSAL->getMaxCacheSize(), MAX_RESULTS); 107 // Create a new dic node here 108 DicNode rootNode; 109 DicNodeUtils::initAsRoot(traverseSession->getDicRootPos(), 110 traverseSession->getOffsetDict(), traverseSession->getPrevWordPos(), &rootNode); 111 traverseSession->getDicTraverseCache()->copyPushActive(&rootNode); 112 } 113 } 114 115 /** 116 * Outputs the final list of suggestions (i.e., terminal nodes). 117 */ 118 int Suggest::outputSuggestions(DicTraverseSession *traverseSession, int *frequencies, 119 int *outputCodePoints, int *spaceIndices, int *outputTypes) const { 120 #if DEBUG_EVALUATE_MOST_PROBABLE_STRING 121 const int terminalSize = 0; 122 #else 123 const int terminalSize = min(MAX_RESULTS, 124 static_cast<int>(traverseSession->getDicTraverseCache()->terminalSize())); 125 #endif 126 DicNode terminals[MAX_RESULTS]; // Avoiding non-POD variable length array 127 128 for (int index = terminalSize - 1; index >= 0; --index) { 129 traverseSession->getDicTraverseCache()->popTerminal(&terminals[index]); 130 } 131 132 const float languageWeight = SCORING->getAdjustedLanguageWeight( 133 traverseSession, terminals, terminalSize); 134 135 int outputWordIndex = 0; 136 // Insert most probable word at index == 0 as long as there is one terminal at least 137 const bool hasMostProbableString = 138 SCORING->getMostProbableString(traverseSession, terminalSize, languageWeight, 139 &outputCodePoints[0], &outputTypes[0], &frequencies[0]); 140 if (hasMostProbableString) { 141 ++outputWordIndex; 142 } 143 144 // Initial value of the loop index for terminal nodes (words) 145 int doubleLetterTerminalIndex = -1; 146 DoubleLetterLevel doubleLetterLevel = NOT_A_DOUBLE_LETTER; 147 SCORING->searchWordWithDoubleLetter(terminals, terminalSize, 148 &doubleLetterTerminalIndex, &doubleLetterLevel); 149 150 int maxScore = S_INT_MIN; 151 // Output suggestion results here 152 for (int terminalIndex = 0; terminalIndex < terminalSize && outputWordIndex < MAX_RESULTS; 153 ++terminalIndex) { 154 DicNode *terminalDicNode = &terminals[terminalIndex]; 155 if (DEBUG_GEO_FULL) { 156 terminalDicNode->dump("OUT:"); 157 } 158 const float doubleLetterCost = SCORING->getDoubleLetterDemotionDistanceCost( 159 terminalIndex, doubleLetterTerminalIndex, doubleLetterLevel); 160 const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight) 161 + doubleLetterCost; 162 const TerminalAttributes terminalAttributes(traverseSession->getOffsetDict(), 163 terminalDicNode->getFlags(), terminalDicNode->getAttributesPos()); 164 const bool isPossiblyOffensiveWord = terminalDicNode->getProbability() <= 0; 165 const bool isExactMatch = terminalDicNode->isExactMatch(); 166 const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase(); 167 // Heuristic: We exclude freq=0 first-char-uppercase words from exact match. 168 // (e.g. "AMD" and "and") 169 const bool isSafeExactMatch = isExactMatch 170 && !(isPossiblyOffensiveWord && isFirstCharUppercase); 171 const int outputTypeFlags = 172 (isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0) 173 | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0); 174 175 // Entries that are blacklisted or do not represent a word should not be output. 176 const bool isValidWord = !terminalAttributes.isBlacklistedOrNotAWord(); 177 178 // Increase output score of top typing suggestion to ensure autocorrection. 179 // TODO: Better integration with java side autocorrection logic. 180 // Force autocorrection for obvious long multi-word suggestions. 181 const bool isForceCommitMultiWords = TRAVERSAL->allowPartialCommit() 182 && (traverseSession->isPartiallyCommited() 183 || (traverseSession->getInputSize() >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT 184 && terminalDicNode->hasMultipleWords())); 185 186 const int finalScore = SCORING->calculateFinalScore( 187 compoundDistance, traverseSession->getInputSize(), 188 isForceCommitMultiWords || (isValidWord && SCORING->doesAutoCorrectValidWord())); 189 190 maxScore = max(maxScore, finalScore); 191 192 if (TRAVERSAL->allowPartialCommit()) { 193 // Index for top typing suggestion should be 0. 194 if (isValidWord && outputWordIndex == 0) { 195 terminalDicNode->outputSpacePositionsResult(spaceIndices); 196 } 197 } 198 199 // Don't output invalid words. However, we still need to submit their shortcuts if any. 200 if (isValidWord) { 201 outputTypes[outputWordIndex] = Dictionary::KIND_CORRECTION | outputTypeFlags; 202 frequencies[outputWordIndex] = finalScore; 203 // Populate the outputChars array with the suggested word. 204 const int startIndex = outputWordIndex * MAX_WORD_LENGTH; 205 terminalDicNode->outputResult(&outputCodePoints[startIndex]); 206 ++outputWordIndex; 207 } 208 209 const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode); 210 outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex, 211 finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped); 212 DicNode::managedDelete(terminalDicNode); 213 } 214 215 if (hasMostProbableString) { 216 SCORING->safetyNetForMostProbableString(terminalSize, maxScore, 217 &outputCodePoints[0], &frequencies[0]); 218 } 219 return outputWordIndex; 220 } 221 222 /** 223 * Expands the dicNodes in the current search priority queue by advancing to the possible child 224 * nodes based on the next touch point(s) (or no touch points for lookahead) 225 */ 226 void Suggest::expandCurrentDicNodes(DicTraverseSession *traverseSession) const { 227 const int inputSize = traverseSession->getInputSize(); 228 DicNodeVector childDicNodes(TRAVERSAL->getDefaultExpandDicNodeSize()); 229 DicNode correctionDicNode; 230 231 // TODO: Find more efficient caching 232 const bool shouldDepthLevelCache = TRAVERSAL->shouldDepthLevelCache(traverseSession); 233 if (shouldDepthLevelCache) { 234 traverseSession->getDicTraverseCache()->updateLastCachedInputIndex(); 235 } 236 if (DEBUG_CACHE) { 237 AKLOGI("expandCurrentDicNodes depth level cache = %d, inputSize = %d", 238 shouldDepthLevelCache, inputSize); 239 } 240 while (traverseSession->getDicTraverseCache()->activeSize() > 0) { 241 DicNode dicNode; 242 traverseSession->getDicTraverseCache()->popActive(&dicNode); 243 if (dicNode.isTotalInputSizeExceedingLimit()) { 244 return; 245 } 246 childDicNodes.clear(); 247 const int point0Index = dicNode.getInputIndex(0); 248 const bool canDoLookAheadCorrection = 249 TRAVERSAL->canDoLookAheadCorrection(traverseSession, &dicNode); 250 const bool isLookAheadCorrection = canDoLookAheadCorrection 251 && traverseSession->getDicTraverseCache()-> 252 isLookAheadCorrectionInputIndex(static_cast<int>(point0Index)); 253 const bool isCompletion = dicNode.isCompletion(inputSize); 254 255 const bool shouldNodeLevelCache = 256 TRAVERSAL->shouldNodeLevelCache(traverseSession, &dicNode); 257 if (shouldDepthLevelCache || shouldNodeLevelCache) { 258 if (DEBUG_CACHE) { 259 dicNode.dump("PUSH_CACHE"); 260 } 261 traverseSession->getDicTraverseCache()->copyPushContinue(&dicNode); 262 dicNode.setCached(); 263 } 264 265 if (dicNode.isInDigraph()) { 266 // Finish digraph handling if the node is in the middle of a digraph expansion. 267 processDicNodeAsDigraph(traverseSession, &dicNode); 268 } else if (isLookAheadCorrection) { 269 // The algorithm maintains a small set of "deferred" nodes that have not consumed the 270 // latest touch point yet. These are needed to apply look-ahead correction operations 271 // that require special handling of the latest touch point. For example, with insertions 272 // (e.g., "thiis" -> "this") the latest touch point should not be consumed at all. 273 processDicNodeAsTransposition(traverseSession, &dicNode); 274 processDicNodeAsInsertion(traverseSession, &dicNode); 275 } else { // !isLookAheadCorrection 276 // Only consider typing error corrections if the normalized compound distance is 277 // below a spatial distance threshold. 278 // NOTE: the threshold may need to be updated if scoring model changes. 279 // TODO: Remove. Do not prune node here. 280 const bool allowsErrorCorrections = TRAVERSAL->allowsErrorCorrections(&dicNode); 281 // Process for handling space substitution (e.g., hevis => he is) 282 if (allowsErrorCorrections 283 && TRAVERSAL->isSpaceSubstitutionTerminal(traverseSession, &dicNode)) { 284 createNextWordDicNode(traverseSession, &dicNode, true /* spaceSubstitution */); 285 } 286 287 DicNodeUtils::getAllChildDicNodes( 288 &dicNode, traverseSession->getOffsetDict(), &childDicNodes); 289 290 const int childDicNodesSize = childDicNodes.getSizeAndLock(); 291 for (int i = 0; i < childDicNodesSize; ++i) { 292 DicNode *const childDicNode = childDicNodes[i]; 293 if (isCompletion) { 294 // Handle forward lookahead when the lexicon letter exceeds the input size. 295 processDicNodeAsMatch(traverseSession, childDicNode); 296 continue; 297 } 298 if (DigraphUtils::hasDigraphForCodePoint(traverseSession->getDictFlags(), 299 childDicNode->getNodeCodePoint())) { 300 correctionDicNode.initByCopy(childDicNode); 301 correctionDicNode.advanceDigraphIndex(); 302 processDicNodeAsDigraph(traverseSession, &correctionDicNode); 303 } 304 if (TRAVERSAL->isOmission(traverseSession, &dicNode, childDicNode, 305 allowsErrorCorrections)) { 306 // TODO: (Gesture) Change weight between omission and substitution errors 307 // TODO: (Gesture) Terminal node should not be handled as omission 308 correctionDicNode.initByCopy(childDicNode); 309 processDicNodeAsOmission(traverseSession, &correctionDicNode); 310 } 311 const ProximityType proximityType = TRAVERSAL->getProximityType( 312 traverseSession, &dicNode, childDicNode); 313 switch (proximityType) { 314 // TODO: Consider the difference of proximityType here 315 case MATCH_CHAR: 316 case PROXIMITY_CHAR: 317 processDicNodeAsMatch(traverseSession, childDicNode); 318 break; 319 case ADDITIONAL_PROXIMITY_CHAR: 320 if (allowsErrorCorrections) { 321 processDicNodeAsAdditionalProximityChar(traverseSession, &dicNode, 322 childDicNode); 323 } 324 break; 325 case SUBSTITUTION_CHAR: 326 if (allowsErrorCorrections) { 327 processDicNodeAsSubstitution(traverseSession, &dicNode, childDicNode); 328 } 329 break; 330 case UNRELATED_CHAR: 331 // Just drop this node and do nothing. 332 break; 333 default: 334 // Just drop this node and do nothing. 335 break; 336 } 337 } 338 339 // Push the node for look-ahead correction 340 if (allowsErrorCorrections && canDoLookAheadCorrection) { 341 traverseSession->getDicTraverseCache()->copyPushNextActive(&dicNode); 342 } 343 } 344 } 345 } 346 347 void Suggest::processTerminalDicNode( 348 DicTraverseSession *traverseSession, DicNode *dicNode) const { 349 if (dicNode->getCompoundDistance() >= static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) { 350 return; 351 } 352 if (!dicNode->isTerminalWordNode()) { 353 return; 354 } 355 if (TRAVERSAL->needsToTraverseAllUserInput() 356 && dicNode->getInputIndex(0) < traverseSession->getInputSize()) { 357 return; 358 } 359 360 if (dicNode->shouldBeFilterdBySafetyNetForBigram()) { 361 return; 362 } 363 // Create a non-cached node here. 364 DicNode terminalDicNode; 365 DicNodeUtils::initByCopy(dicNode, &terminalDicNode); 366 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TERMINAL, traverseSession, 0, 367 &terminalDicNode, traverseSession->getMultiBigramMap()); 368 traverseSession->getDicTraverseCache()->copyPushTerminal(&terminalDicNode); 369 } 370 371 /** 372 * Adds the expanded dicNode to the next search priority queue. Also creates an additional next word 373 * (by the space omission error correction) search path if input dicNode is on a terminal node. 374 */ 375 void Suggest::processExpandedDicNode( 376 DicTraverseSession *traverseSession, DicNode *dicNode) const { 377 processTerminalDicNode(traverseSession, dicNode); 378 if (dicNode->getCompoundDistance() < static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) { 379 if (TRAVERSAL->isSpaceOmissionTerminal(traverseSession, dicNode)) { 380 createNextWordDicNode(traverseSession, dicNode, false /* spaceSubstitution */); 381 } 382 const int allowsLookAhead = !(dicNode->hasMultipleWords() 383 && dicNode->isCompletion(traverseSession->getInputSize())); 384 if (dicNode->hasChildren() && allowsLookAhead) { 385 traverseSession->getDicTraverseCache()->copyPushNextActive(dicNode); 386 } 387 } 388 DicNode::managedDelete(dicNode); 389 } 390 391 void Suggest::processDicNodeAsMatch(DicTraverseSession *traverseSession, 392 DicNode *childDicNode) const { 393 weightChildNode(traverseSession, childDicNode); 394 processExpandedDicNode(traverseSession, childDicNode); 395 } 396 397 void Suggest::processDicNodeAsAdditionalProximityChar(DicTraverseSession *traverseSession, 398 DicNode *dicNode, DicNode *childDicNode) const { 399 // Note: Most types of corrections don't need to look up the bigram information since they do 400 // not treat the node as a terminal. There is no need to pass the bigram map in these cases. 401 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_ADDITIONAL_PROXIMITY, 402 traverseSession, dicNode, childDicNode, 0 /* multiBigramMap */); 403 weightChildNode(traverseSession, childDicNode); 404 processExpandedDicNode(traverseSession, childDicNode); 405 } 406 407 void Suggest::processDicNodeAsSubstitution(DicTraverseSession *traverseSession, 408 DicNode *dicNode, DicNode *childDicNode) const { 409 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_SUBSTITUTION, traverseSession, 410 dicNode, childDicNode, 0 /* multiBigramMap */); 411 weightChildNode(traverseSession, childDicNode); 412 processExpandedDicNode(traverseSession, childDicNode); 413 } 414 415 // Process the node codepoint as a digraph. This means that composite glyphs like the German 416 // u-umlaut is expanded to the transliteration "ue". Note that this happens in parallel with 417 // the normal non-digraph traversal, so both "uber" and "ueber" can be corrected to "[u-umlaut]ber". 418 void Suggest::processDicNodeAsDigraph(DicTraverseSession *traverseSession, 419 DicNode *childDicNode) const { 420 weightChildNode(traverseSession, childDicNode); 421 childDicNode->advanceDigraphIndex(); 422 processExpandedDicNode(traverseSession, childDicNode); 423 } 424 425 /** 426 * Handle the dicNode as an omission error (e.g., ths => this). Skip the current letter and consider 427 * matches for all possible next letters. Note that just skipping the current letter without any 428 * other conditions tends to flood the search dic nodes cache with omission nodes. Instead, check 429 * the possible *next* letters after the omission to better limit search to plausible omissions. 430 * Note that apostrophes are handled as omissions. 431 */ 432 void Suggest::processDicNodeAsOmission( 433 DicTraverseSession *traverseSession, DicNode *dicNode) const { 434 DicNodeVector childDicNodes; 435 DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getOffsetDict(), &childDicNodes); 436 437 const int size = childDicNodes.getSizeAndLock(); 438 for (int i = 0; i < size; i++) { 439 DicNode *const childDicNode = childDicNodes[i]; 440 // Treat this word as omission 441 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_OMISSION, traverseSession, 442 dicNode, childDicNode, 0 /* multiBigramMap */); 443 weightChildNode(traverseSession, childDicNode); 444 445 if (!TRAVERSAL->isPossibleOmissionChildNode(traverseSession, dicNode, childDicNode)) { 446 continue; 447 } 448 processExpandedDicNode(traverseSession, childDicNode); 449 } 450 } 451 452 /** 453 * Handle the dicNode as an insertion error (e.g., thiis => this). Skip the current touch point and 454 * consider matches for the next touch point. 455 */ 456 void Suggest::processDicNodeAsInsertion(DicTraverseSession *traverseSession, 457 DicNode *dicNode) const { 458 const int16_t pointIndex = dicNode->getInputIndex(0); 459 DicNodeVector childDicNodes; 460 DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getOffsetDict(), 461 traverseSession->getProximityInfoState(0), pointIndex + 1, true, &childDicNodes); 462 const int size = childDicNodes.getSizeAndLock(); 463 for (int i = 0; i < size; i++) { 464 DicNode *const childDicNode = childDicNodes[i]; 465 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_INSERTION, traverseSession, 466 dicNode, childDicNode, 0 /* multiBigramMap */); 467 processExpandedDicNode(traverseSession, childDicNode); 468 } 469 } 470 471 /** 472 * Handle the dicNode as a transposition error (e.g., thsi => this). Swap the next two touch points. 473 */ 474 void Suggest::processDicNodeAsTransposition(DicTraverseSession *traverseSession, 475 DicNode *dicNode) const { 476 const int16_t pointIndex = dicNode->getInputIndex(0); 477 DicNodeVector childDicNodes1; 478 DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getOffsetDict(), 479 traverseSession->getProximityInfoState(0), pointIndex + 1, false, &childDicNodes1); 480 const int childSize1 = childDicNodes1.getSizeAndLock(); 481 for (int i = 0; i < childSize1; i++) { 482 if (childDicNodes1[i]->hasChildren()) { 483 DicNodeVector childDicNodes2; 484 DicNodeUtils::getProximityChildDicNodes( 485 childDicNodes1[i], traverseSession->getOffsetDict(), 486 traverseSession->getProximityInfoState(0), pointIndex, false, &childDicNodes2); 487 const int childSize2 = childDicNodes2.getSizeAndLock(); 488 for (int j = 0; j < childSize2; j++) { 489 DicNode *const childDicNode2 = childDicNodes2[j]; 490 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TRANSPOSITION, 491 traverseSession, childDicNodes1[i], childDicNode2, 0 /* multiBigramMap */); 492 processExpandedDicNode(traverseSession, childDicNode2); 493 } 494 } 495 DicNode::managedDelete(childDicNodes1[i]); 496 } 497 } 498 499 /** 500 * Weight child node by aligning it to the key 501 */ 502 void Suggest::weightChildNode(DicTraverseSession *traverseSession, DicNode *dicNode) const { 503 const int inputSize = traverseSession->getInputSize(); 504 if (dicNode->isCompletion(inputSize)) { 505 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_COMPLETION, traverseSession, 506 0 /* parentDicNode */, dicNode, 0 /* multiBigramMap */); 507 } else { // completion 508 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_MATCH, traverseSession, 509 0 /* parentDicNode */, dicNode, 0 /* multiBigramMap */); 510 } 511 } 512 513 /** 514 * Creates a new dicNode that represents a space insertion at the end of the input dicNode. Also 515 * incorporates the unigram / bigram score for the ending word into the new dicNode. 516 */ 517 void Suggest::createNextWordDicNode(DicTraverseSession *traverseSession, DicNode *dicNode, 518 const bool spaceSubstitution) const { 519 if (!TRAVERSAL->isGoodToTraverseNextWord(dicNode)) { 520 return; 521 } 522 523 // Create a non-cached node here. 524 DicNode newDicNode; 525 DicNodeUtils::initAsRootWithPreviousWord(traverseSession->getDicRootPos(), 526 traverseSession->getOffsetDict(), dicNode, &newDicNode); 527 const CorrectionType correctionType = spaceSubstitution ? 528 CT_NEW_WORD_SPACE_SUBSTITUTION : CT_NEW_WORD_SPACE_OMITTION; 529 Weighting::addCostAndForwardInputIndex(WEIGHTING, correctionType, traverseSession, dicNode, 530 &newDicNode, traverseSession->getMultiBigramMap()); 531 traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode); 532 } 533 } // namespace latinime 534