Home | History | Annotate | Download | only in core
      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