blob: 3bfbfad25a68cd6186dd48203af6b760e6a85c5b [file] [log] [blame]
satok30088252010-12-01 21:22:15 +09001/*
2**
3** Copyright 2010, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
satok18c28f42010-12-02 18:11:54 +090018#include <string.h>
19
satoke808e432010-12-02 14:53:24 +090020#define LOG_TAG "LatinIME: bigram_dictionary.cpp"
21
satok30088252010-12-01 21:22:15 +090022#include "bigram_dictionary.h"
Jean Chalard588e2f22011-07-25 14:03:19 +090023#include "binary_format.h"
Jean Chalard49ba1352012-05-07 20:14:00 +090024#include "bloom_filter.h"
Ken Wakasa3b088a22012-05-16 23:05:32 +090025#include "defines.h"
Jean Chalard49ba1352012-05-07 20:14:00 +090026#include "dictionary.h"
satok30088252010-12-01 21:22:15 +090027
28namespace latinime {
29
satokb1ed1d42012-06-14 16:35:23 -070030BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength)
31 : DICT(dict), MAX_WORD_LENGTH(maxWordLength) {
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
satokb1ed1d42012-06-14 16:35:23 -070040bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequency,
41 const int maxBigrams, int *bigramFreq, unsigned short *bigramChars) const {
satok18c28f42010-12-02 18:11:54 +090042 word[length] = 0;
43 if (DEBUG_DICT) {
Doug Kwance9efbf2011-07-07 22:53:50 -070044#ifdef FLAG_DBG
satok18c28f42010-12-02 18:11:54 +090045 char s[length + 1];
46 for (int i = 0; i <= length; i++) s[i] = word[i];
satok9fb6f472012-01-13 18:01:22 +090047 AKLOGI("Bigram: Found word = %s, freq = %d :", s, frequency);
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;
satokb1ed1d42012-06-14 16:35:23 -070053 while (insertAt < maxBigrams) {
54 if (frequency > bigramFreq[insertAt] || (bigramFreq[insertAt] == frequency
55 && length < Dictionary::wideStrLen(bigramChars + insertAt * MAX_WORD_LENGTH))) {
satok18c28f42010-12-02 18:11:54 +090056 break;
57 }
58 insertAt++;
59 }
Ken Wakasade3070a2011-03-19 09:16:42 +090060 if (DEBUG_DICT) {
satokb1ed1d42012-06-14 16:35:23 -070061 AKLOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, maxBigrams);
Ken Wakasade3070a2011-03-19 09:16:42 +090062 }
satokb1ed1d42012-06-14 16:35:23 -070063 if (insertAt < maxBigrams) {
64 memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
65 (char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
66 (maxBigrams - insertAt - 1) * sizeof(bigramFreq[0]));
67 bigramFreq[insertAt] = frequency;
68 memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
69 (char*) bigramChars + (insertAt ) * MAX_WORD_LENGTH * sizeof(short),
70 (maxBigrams - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
71 unsigned short *dest = bigramChars + (insertAt ) * MAX_WORD_LENGTH;
satok18c28f42010-12-02 18:11:54 +090072 while (length--) {
73 *dest++ = *word++;
74 }
75 *dest = 0; // NULL terminate
Ken Wakasade3070a2011-03-19 09:16:42 +090076 if (DEBUG_DICT) {
satok9fb6f472012-01-13 18:01:22 +090077 AKLOGI("Bigram: Added word at %d", insertAt);
Ken Wakasade3070a2011-03-19 09:16:42 +090078 }
satok18c28f42010-12-02 18:11:54 +090079 return true;
80 }
81 return false;
82}
83
Jean Chalard588e2f22011-07-25 14:03:19 +090084/* Parameters :
85 * prevWord: the word before, the one for which we need to look up bigrams.
86 * prevWordLength: its length.
satokb1ed1d42012-06-14 16:35:23 -070087 * inputCodes: what user typed, in the same format as for UnigramDictionary::getSuggestions.
Jean Chalard588e2f22011-07-25 14:03:19 +090088 * codesSize: the size of the codes array.
89 * bigramChars: an array for output, at the same format as outwords for getSuggestions.
90 * bigramFreq: an array to output frequencies.
91 * maxWordLength: the maximum size of a word.
92 * maxBigrams: the maximum number of bigrams fitting in the bigramChars array.
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 */
satokb1ed1d42012-06-14 16:35:23 -0700101int BigramDictionary::getBigrams(const int32_t *prevWord, int prevWordLength, int *inputCodes,
satok18c28f42010-12-02 18:11:54 +0900102 int codesSize, unsigned short *bigramChars, int *bigramFreq, int maxWordLength,
satokb1ed1d42012-06-14 16:35:23 -0700103 int maxBigrams) const {
Jean Chalard588e2f22011-07-25 14:03:19 +0900104 // TODO: remove unused arguments, and refrain from storing stuff in members of this class
105 // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
satok18c28f42010-12-02 18:11:54 +0900106
Jean Chalard588e2f22011-07-25 14:03:19 +0900107 const uint8_t* const root = DICT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900108 int pos = getBigramListPositionForWord(prevWord, prevWordLength,
109 false /* forceLowerCaseSearch */);
Jean Chalard351864b2012-04-24 18:06:51 +0900110 // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
Jean Chalarde9a86e22012-06-28 21:01:29 +0900111 if (0 == pos) {
112 // If no bigrams for this exact word, search again in lower case.
113 pos = getBigramListPositionForWord(prevWord, prevWordLength,
114 true /* forceLowerCaseSearch */);
115 }
116 // If still no bigrams, we really don't have them!
Jean Chalardee396df2012-04-17 16:02:35 +0900117 if (0 == pos) return 0;
Jean Chalard588e2f22011-07-25 14:03:19 +0900118 int bigramFlags;
119 int bigramCount = 0;
120 do {
121 bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
122 uint16_t bigramBuffer[MAX_WORD_LENGTH];
Jean Chalard62cd9192012-05-30 14:46:43 +0900123 int unigramFreq = 0;
Jean Chalard588e2f22011-07-25 14:03:19 +0900124 const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
125 &pos);
126 const int length = BinaryFormat::getWordAtAddress(root, bigramPos, MAX_WORD_LENGTH,
Jean Chalarde3084592012-05-29 16:22:46 +0900127 bigramBuffer, &unigramFreq);
satok18c28f42010-12-02 18:11:54 +0900128
Jean Chalardad290d62012-02-15 19:22:26 -0800129 // codesSize == 0 means we are trying to find bigram predictions.
satokb1ed1d42012-06-14 16:35:23 -0700130 if (codesSize < 1 || checkFirstCharacter(bigramBuffer, inputCodes)) {
131 const int bigramFreqTemp = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags;
Jean Chalard46fe49f2012-05-29 16:50:25 +0900132 // Due to space constraints, the frequency for bigrams is approximate - the lower the
133 // unigram frequency, the worse the precision. The theoritical maximum error in
134 // resulting frequency is 8 - although in the practice it's never bigger than 3 or 4
135 // in very bad cases. This means that sometimes, we'll see some bigrams interverted
136 // here, but it can't get too bad.
Jean Chalarde3084592012-05-29 16:22:46 +0900137 const int frequency =
satokb1ed1d42012-06-14 16:35:23 -0700138 BinaryFormat::computeFrequencyForBigram(unigramFreq, bigramFreqTemp);
139 if (addWordBigram(
140 bigramBuffer, length, frequency, maxBigrams, bigramFreq, bigramChars)) {
Jean Chalard9715cc42012-03-21 15:26:45 +0900141 ++bigramCount;
142 }
satok18c28f42010-12-02 18:11:54 +0900143 }
Tom Ouyang4d289d32012-04-26 23:50:21 -0700144 } while (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
Jean Chalard588e2f22011-07-25 14:03:19 +0900145 return bigramCount;
satok18c28f42010-12-02 18:11:54 +0900146}
147
Jean Chalardee396df2012-04-17 16:02:35 +0900148// Returns a pointer to the start of the bigram list.
149// If the word is not found or has no bigrams, this function returns 0.
Jean Chalard351864b2012-04-24 18:06:51 +0900150int BigramDictionary::getBigramListPositionForWord(const int32_t *prevWord,
Jean Chalarde9a86e22012-06-28 21:01:29 +0900151 const int prevWordLength, const bool forceLowerCaseSearch) const {
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900152 if (0 >= prevWordLength) return 0;
Jean Chalard351864b2012-04-24 18:06:51 +0900153 const uint8_t* const root = DICT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900154 int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength,
155 forceLowerCaseSearch);
Jean Chalard9c2a96a2012-04-17 11:38:44 +0900156
157 if (NOT_VALID_WORD == pos) return 0;
158 const int flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
159 if (0 == (flags & UnigramDictionary::FLAG_HAS_BIGRAMS)) return 0;
160 if (0 == (flags & UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS)) {
161 BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
162 } else {
163 pos = BinaryFormat::skipOtherCharacters(root, pos);
164 }
Jean Chalard9c2a96a2012-04-17 11:38:44 +0900165 pos = BinaryFormat::skipFrequency(flags, pos);
Jean Chalard402b0572012-05-29 15:56:30 +0900166 pos = BinaryFormat::skipChildrenPosition(flags, pos);
Jean Chalard9c2a96a2012-04-17 11:38:44 +0900167 pos = BinaryFormat::skipShortcuts(root, flags, pos);
168 return pos;
169}
170
Jean Chalardf1634c82012-05-02 19:05:27 +0900171void BigramDictionary::fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord,
satokb1ed1d42012-06-14 16:35:23 -0700172 const int prevWordLength, std::map<int, int> *map, uint8_t *filter) const {
Jean Chalardf1634c82012-05-02 19:05:27 +0900173 memset(filter, 0, BIGRAM_FILTER_BYTE_SIZE);
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900174 const uint8_t* const root = DICT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900175 int pos = getBigramListPositionForWord(prevWord, prevWordLength,
176 false /* forceLowerCaseSearch */);
177 if (0 == pos) {
178 // If no bigrams for this exact string, search again in lower case.
179 pos = getBigramListPositionForWord(prevWord, prevWordLength,
180 true /* forceLowerCaseSearch */);
181 }
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900182 if (0 == pos) return;
183
184 int bigramFlags;
185 do {
186 bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
187 const int frequency = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags;
188 const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
189 &pos);
190 (*map)[bigramPos] = frequency;
Jean Chalardf1634c82012-05-02 19:05:27 +0900191 setInFilter(filter, bigramPos);
Jean Chalard1ff8dc42012-05-02 16:00:24 +0900192 } while (0 != (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags));
193}
194
satokb1ed1d42012-06-14 16:35:23 -0700195bool BigramDictionary::checkFirstCharacter(unsigned short *word, int *inputCodes) const {
satok18c28f42010-12-02 18:11:54 +0900196 // Checks whether this word starts with same character or neighboring characters of
197 // what user typed.
198
satok18c28f42010-12-02 18:11:54 +0900199 int maxAlt = MAX_ALTERNATIVES;
Tom Ouyangaeda8a72012-03-24 15:31:27 +0900200 const unsigned short firstBaseChar = toBaseLowerCase(*word);
satok18c28f42010-12-02 18:11:54 +0900201 while (maxAlt > 0) {
Tom Ouyangaeda8a72012-03-24 15:31:27 +0900202 if (toBaseLowerCase(*inputCodes) == firstBaseChar) {
satok18c28f42010-12-02 18:11:54 +0900203 return true;
204 }
205 inputCodes++;
206 maxAlt--;
207 }
208 return false;
209}
210
Tom Ouyang4d289d32012-04-26 23:50:21 -0700211bool BigramDictionary::isValidBigram(const int32_t *word1, int length1, const int32_t *word2,
satokb1ed1d42012-06-14 16:35:23 -0700212 int length2) const {
Tom Ouyang4d289d32012-04-26 23:50:21 -0700213 const uint8_t* const root = DICT;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900214 int pos = getBigramListPositionForWord(word1, length1, false /* forceLowerCaseSearch */);
Tom Ouyang4d289d32012-04-26 23:50:21 -0700215 // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
216 if (0 == pos) return false;
Jean Chalarde9a86e22012-06-28 21:01:29 +0900217 int nextWordPos = BinaryFormat::getTerminalPosition(root, word2, length2,
218 false /* forceLowerCaseSearch */);
Tom Ouyang4d289d32012-04-26 23:50:21 -0700219 if (NOT_VALID_WORD == nextWordPos) return false;
220 int bigramFlags;
221 do {
222 bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
223 const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
224 &pos);
225 if (bigramPos == nextWordPos) {
226 return true;
227 }
228 } while (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
229 return false;
230}
231
satok30088252010-12-01 21:22:15 +0900232// TODO: Move functions related to bigram to here
233} // namespace latinime