1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 // NOTE(tabata): This code is used mainly by louds trie builder to build
31 // dictionary. Please check error handling, if you want to include
32 // this to run within client.
33 
34 #include "dictionary/text_dictionary_loader.h"
35 
36 #include <algorithm>
37 #include <limits>
38 #include <memory>
39 #include <string>
40 #include <vector>
41 
42 #include "base/file_stream.h"
43 #include "base/flags.h"
44 #include "base/iterator_adapter.h"
45 #include "base/logging.h"
46 #include "base/multifile.h"
47 #include "base/number_util.h"
48 #include "base/stl_util.h"
49 #include "base/string_piece.h"
50 #include "base/util.h"
51 #include "dictionary/dictionary_token.h"
52 #include "dictionary/pos_matcher.h"
53 
54 DEFINE_int32(tokens_reserve_size, 1400000,
55              "Reserve the specified size of token buffer in advance.");
56 
57 namespace mozc {
58 namespace dictionary {
59 namespace {
60 
61 // Functor to sort a sequence of Tokens first by value and then by key.
62 struct OrderByValueThenByKey {
operator ()mozc::dictionary::__anon004b61d00111::OrderByValueThenByKey63   bool operator()(const Token *l, const Token *r) const {
64     const int comp = l->value.compare(r->value);
65     return comp == 0 ? (l->key < r->key) : (comp < 0);
66   }
67 };
68 
69 // Provides a view of Token iterator as a pair of value and key.  Used to look
70 // up tokens from a sorted range of Token pointers using value and key.
71 struct AsValueAndKey
72     : public AdapterBase<std::pair<StringPiece, StringPiece> > {
operator ()mozc::dictionary::__anon004b61d00111::AsValueAndKey73   value_type operator()(std::vector<Token *>::const_iterator iter) const {
74     const Token *token = *iter;
75     return std::pair<StringPiece, StringPiece>(token->value, token->key);
76   }
77 };
78 
79 // Provides a view of Token iterator as a value string.  Used to look up tokens
80 // from a sorted range of Tokens using value.
81 struct AsValue : public AdapterBase<StringPiece> {
operator ()mozc::dictionary::__anon004b61d00111::AsValue82   value_type operator()(std::vector<Token *>::const_iterator iter) const {
83     return StringPiece((*iter)->value);
84   }
85 };
86 
87 // Parses one line of reading correction file.  Since the result is returned as
88 // StringPieces, |line| needs to outlive |value_key|.
ParseReadingCorrectionTSV(const string & line,std::pair<StringPiece,StringPiece> * value_key)89 void ParseReadingCorrectionTSV(const string &line,
90                                std::pair<StringPiece, StringPiece> *value_key) {
91   // Format: value\terror\tcorrect
92   SplitIterator<SingleDelimiter> iter(line, "\t");
93   CHECK(!iter.Done());
94   value_key->first = iter.Get();
95   iter.Next();
96   CHECK(!iter.Done());
97   value_key->second = iter.Get();
98 }
99 
100 // Helper function to parse an integer from a string.
SafeStrToInt(StringPiece s,int * n)101 inline bool SafeStrToInt(StringPiece s, int *n) {
102   uint32 u32 = 0;
103   const bool ret = NumberUtil::SafeStrToUInt32(s, &u32);
104   *n = u32;
105   return ret;
106 }
107 
108 // Helper functions to get const iterators.
CBegin(const std::vector<Token * > & tokens)109 inline std::vector<Token *>::const_iterator CBegin(
110     const std::vector<Token *> &tokens) {
111   return tokens.begin();
112 }
113 
CEnd(const std::vector<Token * > & tokens)114 inline std::vector<Token *>::const_iterator CEnd(
115     const std::vector<Token *> &tokens) {
116   return tokens.end();
117 }
118 
119 }  // namespace
120 
TextDictionaryLoader(const POSMatcher & pos_matcher)121 TextDictionaryLoader::TextDictionaryLoader(const POSMatcher &pos_matcher)
122     : zipcode_id_(pos_matcher.GetZipcodeId()),
123       isolated_word_id_(pos_matcher.GetIsolatedWordId()) {}
124 
TextDictionaryLoader(uint16 zipcode_id,uint16 isolated_word_id)125 TextDictionaryLoader::TextDictionaryLoader(uint16 zipcode_id,
126                                            uint16 isolated_word_id)
127     : zipcode_id_(zipcode_id), isolated_word_id_(isolated_word_id) {}
128 
~TextDictionaryLoader()129 TextDictionaryLoader::~TextDictionaryLoader() {
130   Clear();
131 }
132 
RewriteSpecialToken(Token * token,StringPiece label) const133 bool TextDictionaryLoader::RewriteSpecialToken(Token *token,
134                                                StringPiece label) const {
135   CHECK(token);
136   if (label.empty()) {
137     return true;
138   }
139   if (Util::StartsWith(label, "SPELLING_CORRECTION")) {
140     token->attributes = Token::SPELLING_CORRECTION;
141     return true;
142   }
143   if (Util::StartsWith(label, "ZIP_CODE")) {
144     token->lid = zipcode_id_;
145     token->rid = zipcode_id_;
146     return true;
147   }
148   if (Util::StartsWith(label, "ENGLISH")) {
149     // TODO(noriyukit): Might be better to use special POS for english words.
150     token->lid = isolated_word_id_;
151     token->rid = isolated_word_id_;
152     return true;
153   }
154   LOG(ERROR) << "Unknown special label: " << label;
155   return false;
156 }
157 
Load(const string & dictionary_filename,const string & reading_correction_filename)158 void TextDictionaryLoader::Load(const string &dictionary_filename,
159                                 const string &reading_correction_filename) {
160   LoadWithLineLimit(dictionary_filename, reading_correction_filename, -1);
161 }
162 
LoadWithLineLimit(const string & dictionary_filename,const string & reading_correction_filename,int limit)163 void TextDictionaryLoader::LoadWithLineLimit(
164     const string &dictionary_filename,
165     const string &reading_correction_filename,
166     int limit) {
167   Clear();
168 
169   // Roughly allocate buffers for Token pointers.
170   if (limit < 0) {
171     tokens_.reserve(FLAGS_tokens_reserve_size);
172     limit = std::numeric_limits<int>::max();
173   } else {
174     tokens_.reserve(limit);
175   }
176 
177   // Read system dictionary.
178   {
179     InputMultiFile file(dictionary_filename);
180     string line;
181     while (limit > 0 && file.ReadLine(&line)) {
182       Util::ChopReturns(&line);
183       Token *token = ParseTSVLine(line);
184       if (token) {
185         tokens_.push_back(token);
186         --limit;
187       }
188     }
189     LOG(INFO) << tokens_.size() << " tokens from " << dictionary_filename;
190   }
191 
192   if (reading_correction_filename.empty() || limit <= 0) {
193     return;
194   }
195 
196   // Prepare for loading reading corrections. We sort |tokens_| first by value
197   // and then by key so that we can perform the following operations both in
198   // O(log(N)), where N is the size of tokens.
199   //   1. Checking the existence of any key-value pairs: This can be done by
200   //      binary-searching for a pair of value and key.
201   //   2. Accessing all the tokens that have the same value: Since tokens are
202   //      also sorted in order of value, this can be done by finding a range of
203   //      tokens that have the same value.
204   std::sort(tokens_.begin(), tokens_.end(), OrderByValueThenByKey());
205 
206   std::vector<Token *> reading_correction_tokens;
207   LoadReadingCorrectionTokens(reading_correction_filename, tokens_,
208                               &limit, &reading_correction_tokens);
209   const size_t tokens_size = tokens_.size();
210   tokens_.resize(tokens_size + reading_correction_tokens.size());
211   for (size_t i = 0; i < reading_correction_tokens.size(); ++i) {
212     // |tokens_| takes the ownership of each allocated token.
213     tokens_[tokens_size + i] = reading_correction_tokens[i];
214   }
215 }
216 
217 // Loads reading correction data into |tokens|.  The second argument is used to
218 // determine costs of reading correction tokens and must be sorted by
219 // OrderByValueThenByKey().  The output tokens are newly allocated and the
220 // caller is responsible to delete them.
LoadReadingCorrectionTokens(const string & reading_correction_filename,const std::vector<Token * > & ref_sorted_tokens,int * limit,std::vector<Token * > * tokens)221 void TextDictionaryLoader::LoadReadingCorrectionTokens(
222     const string &reading_correction_filename,
223     const std::vector<Token *> &ref_sorted_tokens,
224     int *limit,
225     std::vector<Token *> *tokens) {
226   // Load reading correction entries.
227   int reading_correction_size = 0;
228 
229   InputMultiFile file(reading_correction_filename);
230   string line;
231   while (file.ReadLine(&line)) {
232     if (line.empty() || line[0] == '#') {
233       continue;
234     }
235 
236     // Parse TSV line in a pair of value and key (Note: first element is value
237     // and the second key).
238     Util::ChopReturns(&line);
239     std::pair<StringPiece, StringPiece> value_key;
240     ParseReadingCorrectionTSV(line, &value_key);
241 
242     // Filter the entry if this key value pair already exists in the system
243     // dictionary.
244     if (std::binary_search(
245             MakeIteratorAdapter(ref_sorted_tokens.begin(), AsValueAndKey()),
246             MakeIteratorAdapter(ref_sorted_tokens.end(), AsValueAndKey()),
247             value_key)) {
248       VLOG(1) << "System dictionary has the same key-value: " << line;
249       continue;
250     }
251 
252     // Since reading correction entries lack POS and cost, we recover those
253     // fields from a token in the system dictionary that has the same value.
254     // Since multple tokens may have the same value, from such tokens, we
255     // select the one that has the maximum cost.
256     typedef std::vector<Token *>::const_iterator TokenIterator;
257     typedef IteratorAdapter<TokenIterator, AsValue> AsValueIterator;
258     typedef std::pair<AsValueIterator, AsValueIterator> Range;
259     Range range = std::equal_range(
260         MakeIteratorAdapter(CBegin(ref_sorted_tokens), AsValue()),
261         MakeIteratorAdapter(CEnd(ref_sorted_tokens), AsValue()),
262         value_key.first);
263     TokenIterator begin = range.first.base(), end = range.second.base();
264     if (begin == end) {
265       VLOG(1) << "Cannot find the value in system dicitonary - ignored:"
266               << line;
267       continue;
268     }
269     // Now [begin, end) contains all the tokens that have the same value as
270     // this reading correction entry.  Next, find the token that has the
271     // maximum cost in [begin, end).  Note that linear search is sufficiently
272     // fast here because the size of the range is small.
273     const Token *max_cost_token = *begin;
274     for (++begin; begin != end; ++begin) {
275       if ((*begin)->cost > max_cost_token->cost) {
276         max_cost_token = *begin;
277       }
278     }
279 
280     // The cost is calculated as -log(prob) * 500.
281     // We here assume that the wrong reading appear with 1/100 probability
282     // of the original (correct) reading.
283     const int kCostPenalty = 2302;      // -log(1/100) * 500;
284     std::unique_ptr<Token> token(new Token);
285     token->key.assign(value_key.second.data(), value_key.second.size());
286     token->value = max_cost_token->value;
287     token->lid = max_cost_token->lid;
288     token->rid = max_cost_token->rid;
289     token->cost = max_cost_token->cost + kCostPenalty;
290     // We don't set SPELLING_CORRECTION. The entries in reading_correction
291     // data are also stored in rewriter/correction_rewriter.cc.
292     // reading_correction_rewriter annotates the spelling correction
293     // notations.
294     token->attributes = Token::NONE;
295     tokens->push_back(token.release());
296     ++reading_correction_size;
297     if (--*limit <= 0) {
298       break;
299     }
300   }
301   LOG(INFO) << reading_correction_size << " tokens from "
302             << reading_correction_filename;
303 }
304 
Clear()305 void TextDictionaryLoader::Clear() {
306   STLDeleteElements(&tokens_);
307 }
308 
CollectTokens(std::vector<Token * > * res) const309 void TextDictionaryLoader::CollectTokens(std::vector<Token *> *res) const {
310   DCHECK(res);
311   res->reserve(res->size() + tokens_.size());
312   res->insert(res->end(), tokens_.begin(), tokens_.end());
313 }
314 
ParseTSVLine(StringPiece line) const315 Token *TextDictionaryLoader::ParseTSVLine(StringPiece line) const {
316   std::vector<StringPiece> columns;
317   Util::SplitStringUsing(line, "\t", &columns);
318   return ParseTSV(columns);
319 }
320 
ParseTSV(const std::vector<StringPiece> & columns) const321 Token *TextDictionaryLoader::ParseTSV(
322     const std::vector<StringPiece> &columns) const {
323   CHECK_LE(5, columns.size()) << "Lack of columns: " << columns.size();
324 
325   std::unique_ptr<Token> token(new Token);
326 
327   // Parse key, lid, rid, cost, value.
328   Util::NormalizeVoicedSoundMark(columns[0], &token->key);
329   CHECK(SafeStrToInt(columns[1], &token->lid))
330       << "Wrong lid: " << columns[1];
331   CHECK(SafeStrToInt(columns[2], &token->rid))
332       << "Wrong rid: " << columns[2];
333   CHECK(SafeStrToInt(columns[3], &token->cost))
334       << "Wrong cost: " << columns[3];
335   Util::NormalizeVoicedSoundMark(columns[4], &token->value);
336 
337   // Optionally, label (SPELLING_CORRECTION, ZIP_CODE, etc.) may be provided in
338   // column 6.
339   if (columns.size() > 5) {
340     CHECK(RewriteSpecialToken(token.get(), columns[5]))
341         << "Invalid label: " << columns[5];
342   }
343   return token.release();
344 }
345 
346 }  // namespace dictionary
347 }  // namespace mozc
348