blob: ebe27994fa1dff34876d9d21bd3689eae8a6c172 [file] [log] [blame]
satok30088252010-12-01 21:22:15 +09001/*
Ken Wakasa5460ea32012-07-30 16:27:44 +09002 * Copyright (C) 2010, The Android Open Source Project
Ken Wakasa0bbb9172012-07-25 17:51:43 +09003 *
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 */
satok30088252010-12-01 21:22:15 +090016
Ken Wakasaf1008c52012-07-31 17:56:40 +090017#include <cstring>
satok18c28f42010-12-02 18:11:54 +090018
satoke808e432010-12-02 14:53:24 +090019#define LOG_TAG "LatinIME: bigram_dictionary.cpp"
20
satok30088252010-12-01 21:22:15 +090021#include "bigram_dictionary.h"
Ken Wakasaa65c2672013-05-31 00:02:57 +090022
Ken Wakasa3b088a22012-05-16 23:05:32 +090023#include "defines.h"
Ken Wakasaa65c2672013-05-31 00:02:57 +090024#include "suggest/core/dictionary/binary_format.h"
25#include "suggest/core/dictionary/bloom_filter.h"
Ken Wakasa464d3ba2013-05-31 20:25:10 +090026#include "suggest/core/dictionary/char_utils.h"
Ken Wakasaa65c2672013-05-31 00:02:57 +090027#include "suggest/core/dictionary/dictionary.h"
satok30088252010-12-01 21:22:15 +090028
29namespace latinime {
30
Ken Wakasa5db594a2013-01-12 01:18:00 +090031BigramDictionary::BigramDictionary(const uint8_t *const streamStart) : DICT_ROOT(streamStart) {
Ken Wakasade3070a2011-03-19 09:16:42 +090032 if (DEBUG_DICT) {
satok9fb6f472012-01-13 18:01:22 +090033 AKLOGI("BigramDictionary - constructor");
Ken Wakasade3070a2011-03-19 09:16:42 +090034 }
satok30088252010-12-01 21:22:15 +090035}
36
satok18c28f42010-12-02 18:11:54 +090037BigramDictionary::~BigramDictionary() {
satok30088252010-12-01 21:22:15 +090038}
satok18c28f42010-12-02 18:11:54 +090039
Satoshi Kataokae0e67372013-03-18 13:08:31 +090040void BigramDictionary::addWordBigram(int *word, int length, int probability, int *bigramProbability,
Ken Wakasa1e614932012-10-29 18:06:22 +090041 int *bigramCodePoints, int *outputTypes) const {
satok18c28f42010-12-02 18:11:54 +090042 word[length] = 0;
Satoshi Kataokae3207892013-04-08 17:33:32 +090043 if (DEBUG_DICT_FULL) {
Doug Kwance9efbf2011-07-07 22:53:50 -070044#ifdef FLAG_DBG
satok18c28f42010-12-02 18:11:54 +090045 char s[length + 1];
Ken Wakasa1e614932012-10-29 18:06:22 +090046 for (int i = 0; i <= length; i++) s[i] = static_cast<char>(word[i]);
Satoshi Kataokae0e67372013-03-18 13:08:31 +090047 AKLOGI("Bigram: Found word = %s, freq = %d :", s, probability);
satok787945b2011-07-14 08:32:57 +090048#endif
satok18c28f42010-12-02 18:11:54 +090049 }
50
51 // Find the right insertion point
52 int insertAt = 0;
Ken Wakasaf6870cc2013-01-11 18:59:01 +090053 while (insertAt < MAX_RESULTS) {
Satoshi Kataokae0e67372013-03-18 13:08:31 +090054 if (probability > bigramProbability[insertAt] || (bigramProbability[insertAt] == probability
Ken Wakasa464d3ba2013-05-31 20:25:10 +090055 && length < CharUtils::getCodePointCount(MAX_WORD_LENGTH,
Ken Wakasa1e614932012-10-29 18:06:22 +090056 bigramCodePoints + insertAt * MAX_WORD_LENGTH))) {
satok18c28f42010-12-02 18:11:54 +090057 break;
58 }
59 insertAt++;
60 }
Satoshi Kataokae3207892013-04-08 17:33:32 +090061 if (DEBUG_DICT_FULL) {
Ken Wakasaf6870cc2013-01-11 18:59:01 +090062 AKLOGI("Bigram: InsertAt -> %d MAX_RESULTS: %d", insertAt, MAX_RESULTS);
Ken Wakasade3070a2011-03-19 09:16:42 +090063 }
Ken Wakasaf6870cc2013-01-11 18:59:01 +090064 if (insertAt >= MAX_RESULTS) {
65 return;
satok18c28f42010-12-02 18:11:54 +090066 }
Satoshi Kataokae0e67372013-03-18 13:08:31 +090067 memmove(bigramProbability + (insertAt + 1),
68 bigramProbability + insertAt,
69 (MAX_RESULTS - insertAt - 1) * sizeof(bigramProbability[0]));
70 bigramProbability[insertAt] = probability;
Ken Wakasaf6870cc2013-01-11 18:59:01 +090071 outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
72 memmove(bigramCodePoints + (insertAt + 1) * MAX_WORD_LENGTH,
73 bigramCodePoints + insertAt * MAX_WORD_LENGTH,
74 (MAX_RESULTS - insertAt - 1) * sizeof(bigramCodePoints[0]) * MAX_WORD_LENGTH);
75 int *dest = bigramCodePoints + insertAt * MAX_WORD_LENGTH;
76 while (length--) {
77 *dest++ = *word++;
78 }
79 *dest = 0; // NULL terminate
Satoshi Kataokae3207892013-04-08 17:33:32 +090080 if (DEBUG_DICT_FULL) {
Ken Wakasaf6870cc2013-01-11 18:59:01 +090081 AKLOGI("Bigram: Added word at %d", insertAt);
82 }
satok18c28f42010-12-02 18:11:54 +090083}
84
Jean Chalard588e2f22011-07-25 14:03:19 +090085/* Parameters :
86 * prevWord: the word before, the one for which we need to look up bigrams.
87 * prevWordLength: its length.
Ken Wakasa5db594a2013-01-12 01:18:00 +090088 * inputCodePoints: what user typed, in the same format as for UnigramDictionary::getSuggestions.
89 * inputSize: the size of the codes array.
Ken Wakasa1e614932012-10-29 18:06:22 +090090 * bigramCodePoints: an array for output, at the same format as outwords for getSuggestions.
Satoshi Kataokae0e67372013-03-18 13:08:31 +090091 * bigramProbability: an array to output frequencies.
Jean Chalard6931df92012-07-12 12:55:48 +090092 * outputTypes: an array to output types.
Jean Chalard588e2f22011-07-25 14:03:19 +090093 * This method returns the number of bigrams this word has, for backward compatibility.
94 * Note: this is not the number of bigrams output in the array, which is the number of
95 * bigrams this word has WHOSE first letter also matches the letter the user typed.
96 * TODO: this may not be a sensible thing to do. It makes sense when the bigrams are
97 * used to match the first letter of the second word, but once the user has typed more
98 * and the bigrams are used to boost unigram result scores, it makes little sense to
99 * reduce their scope to the ones that match the first letter.
100 */
Ken Wakasa5db594a2013-01-12 01:18:00 +0900101int BigramDictionary::getBigrams(const int *prevWord, int prevWordLength, int *inputCodePoints,
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900102 int inputSize, int *bigramCodePoints, int *bigramProbability, int *outputTypes) const {
Jean Chalard588e2f22011-07-25 14:03:19 +0900103 // TODO: remove unused arguments, and refrain from storing stuff in members of this class
104 // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
satok18c28f42010-12-02 18:11:54 +0900105
Ken Wakasa5db594a2013-01-12 01:18:00 +0900106 const uint8_t *const root = DICT_ROOT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900107 int pos = getBigramListPositionForWord(prevWord, prevWordLength,
108 false /* forceLowerCaseSearch */);
Jean Chalard351864b2012-04-24 18:06:51 +0900109 // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
Jean Chalarde9a86e22012-06-28 21:01:29 +0900110 if (0 == pos) {
111 // If no bigrams for this exact word, search again in lower case.
112 pos = getBigramListPositionForWord(prevWord, prevWordLength,
113 true /* forceLowerCaseSearch */);
114 }
115 // If still no bigrams, we really don't have them!
Jean Chalardee396df2012-04-17 16:02:35 +0900116 if (0 == pos) return 0;
Ken Wakasad86d3132012-09-04 20:29:38 +0900117 uint8_t bigramFlags;
Jean Chalard588e2f22011-07-25 14:03:19 +0900118 int bigramCount = 0;
119 do {
120 bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
Ken Wakasa1e614932012-10-29 18:06:22 +0900121 int bigramBuffer[MAX_WORD_LENGTH];
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900122 int unigramProbability = 0;
Jean Chalard588e2f22011-07-25 14:03:19 +0900123 const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
124 &pos);
125 const int length = BinaryFormat::getWordAtAddress(root, bigramPos, MAX_WORD_LENGTH,
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900126 bigramBuffer, &unigramProbability);
satok18c28f42010-12-02 18:11:54 +0900127
Ken Wakasa5db594a2013-01-12 01:18:00 +0900128 // inputSize == 0 means we are trying to find bigram predictions.
129 if (inputSize < 1 || checkFirstCharacter(bigramBuffer, inputCodePoints)) {
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900130 const int bigramProbabilityTemp =
131 BinaryFormat::MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
132 // Due to space constraints, the probability for bigrams is approximate - the lower the
133 // unigram probability, the worse the precision. The theoritical maximum error in
134 // resulting probability is 8 - although in the practice it's never bigger than 3 or 4
Jean Chalard46fe49f2012-05-29 16:50:25 +0900135 // in very bad cases. This means that sometimes, we'll see some bigrams interverted
136 // here, but it can't get too bad.
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900137 const int probability = BinaryFormat::computeProbabilityForBigram(
138 unigramProbability, bigramProbabilityTemp);
139 addWordBigram(bigramBuffer, length, probability, bigramProbability, bigramCodePoints,
Ken Wakasaf6870cc2013-01-11 18:59:01 +0900140 outputTypes);
141 ++bigramCount;
satok18c28f42010-12-02 18:11:54 +0900142 }
Jean Chalard19560502012-07-31 23:45:32 +0900143 } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
Ken Wakasaf6870cc2013-01-11 18:59:01 +0900144 return min(bigramCount, MAX_RESULTS);
satok18c28f42010-12-02 18:11:54 +0900145}
146
Jean Chalardee396df2012-04-17 16:02:35 +0900147// Returns a pointer to the start of the bigram list.
148// If the word is not found or has no bigrams, this function returns 0.
Ken Wakasaaa5a3e82012-12-03 19:54:30 +0900149int BigramDictionary::getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
150 const bool forceLowerCaseSearch) const {
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900151 if (0 >= prevWordLength) return 0;
Ken Wakasa5db594a2013-01-12 01:18:00 +0900152 const uint8_t *const root = DICT_ROOT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900153 int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength,
154 forceLowerCaseSearch);
Jean Chalard9c2a96a2012-04-17 11:38:44 +0900155
156 if (NOT_VALID_WORD == pos) return 0;
Ken Wakasad86d3132012-09-04 20:29:38 +0900157 const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
Jean Chalard19560502012-07-31 23:45:32 +0900158 if (0 == (flags & BinaryFormat::FLAG_HAS_BIGRAMS)) return 0;
159 if (0 == (flags & BinaryFormat::FLAG_HAS_MULTIPLE_CHARS)) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900160 BinaryFormat::getCodePointAndForwardPointer(root, &pos);
Jean Chalard9c2a96a2012-04-17 11:38:44 +0900161 } else {
162 pos = BinaryFormat::skipOtherCharacters(root, pos);
163 }
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900164 pos = BinaryFormat::skipProbability(flags, pos);
Jean Chalard402b0572012-05-29 15:56:30 +0900165 pos = BinaryFormat::skipChildrenPosition(flags, pos);
Jean Chalard9c2a96a2012-04-17 11:38:44 +0900166 pos = BinaryFormat::skipShortcuts(root, flags, pos);
167 return pos;
168}
169
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900170void BigramDictionary::fillBigramAddressToProbabilityMapAndFilter(const int *prevWord,
satokb1ed1d42012-06-14 16:35:23 -0700171 const int prevWordLength, std::map<int, int> *map, uint8_t *filter) const {
Jean Chalardf1634c82012-05-02 19:05:27 +0900172 memset(filter, 0, BIGRAM_FILTER_BYTE_SIZE);
Ken Wakasa5db594a2013-01-12 01:18:00 +0900173 const uint8_t *const root = DICT_ROOT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900174 int pos = getBigramListPositionForWord(prevWord, prevWordLength,
175 false /* forceLowerCaseSearch */);
176 if (0 == pos) {
177 // If no bigrams for this exact string, search again in lower case.
178 pos = getBigramListPositionForWord(prevWord, prevWordLength,
179 true /* forceLowerCaseSearch */);
180 }
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900181 if (0 == pos) return;
182
Ken Wakasad86d3132012-09-04 20:29:38 +0900183 uint8_t bigramFlags;
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900184 do {
185 bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900186 const int probability = BinaryFormat::MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900187 const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
188 &pos);
Satoshi Kataokae0e67372013-03-18 13:08:31 +0900189 (*map)[bigramPos] = probability;
Jean Chalardf1634c82012-05-02 19:05:27 +0900190 setInFilter(filter, bigramPos);
Ken Wakasa866a6ce2013-04-26 19:58:14 +0900191 } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900192}
193
Ken Wakasa5db594a2013-01-12 01:18:00 +0900194bool BigramDictionary::checkFirstCharacter(int *word, int *inputCodePoints) const {
satok18c28f42010-12-02 18:11:54 +0900195 // Checks whether this word starts with same character or neighboring characters of
196 // what user typed.
197
satok18c28f42010-12-02 18:11:54 +0900198 int maxAlt = MAX_ALTERNATIVES;
Ken Wakasa464d3ba2013-05-31 20:25:10 +0900199 const int firstBaseLowerCodePoint = CharUtils::toBaseLowerCase(*word);
satok18c28f42010-12-02 18:11:54 +0900200 while (maxAlt > 0) {
Ken Wakasa464d3ba2013-05-31 20:25:10 +0900201 if (CharUtils::toBaseLowerCase(*inputCodePoints) == firstBaseLowerCodePoint) {
satok18c28f42010-12-02 18:11:54 +0900202 return true;
203 }
Ken Wakasa5db594a2013-01-12 01:18:00 +0900204 inputCodePoints++;
satok18c28f42010-12-02 18:11:54 +0900205 maxAlt--;
206 }
207 return false;
208}
209
Ken Wakasaaa5a3e82012-12-03 19:54:30 +0900210bool BigramDictionary::isValidBigram(const int *word1, int length1, const int *word2,
satokb1ed1d42012-06-14 16:35:23 -0700211 int length2) const {
Ken Wakasa5db594a2013-01-12 01:18:00 +0900212 const uint8_t *const root = DICT_ROOT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900213 int pos = getBigramListPositionForWord(word1, length1, false /* forceLowerCaseSearch */);
Tom Ouyang4d289d32012-04-26 23:50:21 -0700214 // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
215 if (0 == pos) return false;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900216 int nextWordPos = BinaryFormat::getTerminalPosition(root, word2, length2,
217 false /* forceLowerCaseSearch */);
Tom Ouyang4d289d32012-04-26 23:50:21 -0700218 if (NOT_VALID_WORD == nextWordPos) return false;
Ken Wakasad86d3132012-09-04 20:29:38 +0900219 uint8_t bigramFlags;
Tom Ouyang4d289d32012-04-26 23:50:21 -0700220 do {
221 bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
222 const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
223 &pos);
224 if (bigramPos == nextWordPos) {
225 return true;
226 }
Jean Chalard19560502012-07-31 23:45:32 +0900227 } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
Tom Ouyang4d289d32012-04-26 23:50:21 -0700228 return false;
229}
230
satok30088252010-12-01 21:22:15 +0900231// TODO: Move functions related to bigram to here
232} // namespace latinime