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 #include "prediction/predictor.h"
31 
32 #include <algorithm>
33 #include <string>
34 #include <vector>
35 
36 #include "base/flags.h"
37 #include "base/logging.h"
38 #include "converter/segments.h"
39 #include "protocol/commands.pb.h"
40 #include "protocol/config.pb.h"
41 
42 DECLARE_bool(enable_expansion_for_dictionary_predictor);
43 DECLARE_bool(enable_expansion_for_user_history_predictor);
44 
45 namespace mozc {
46 namespace {
47 
48 const int kPredictionSize = 100;
49 // On Mobile mode PREDICTION (including PARTIAL_PREDICTION) behaves like as
50 // conversion so the limit is same as conversion's one.
51 const int kMobilePredictionSize = 200;
52 
GetCandidatesSize(const Segments & segments)53 size_t GetCandidatesSize(const Segments &segments) {
54   if (segments.conversion_segments_size() <= 0) {
55     LOG(ERROR) << "No conversion segments found";
56     return 0;
57   }
58   return segments.conversion_segment(0).candidates_size();
59 }
60 
61 // TODO(taku): Is it OK to check only |zero_query_suggestion| and
62 // |mixed_conversion|?
IsZeroQuery(const ConversionRequest & request)63 bool IsZeroQuery(const ConversionRequest &request) {
64   return request.request().zero_query_suggestion();
65 }
66 
67 }  // namespace
68 
BasePredictor(PredictorInterface * dictionary_predictor,PredictorInterface * user_history_predictor)69 BasePredictor::BasePredictor(PredictorInterface *dictionary_predictor,
70                              PredictorInterface *user_history_predictor)
71     : dictionary_predictor_(dictionary_predictor),
72       user_history_predictor_(user_history_predictor) {
73   DCHECK(dictionary_predictor_.get());
74   DCHECK(user_history_predictor_.get());
75   // TODO(noriyukit): Now the following features are stable, so remove these
76   // flags.
77   FLAGS_enable_expansion_for_dictionary_predictor = true;
78   FLAGS_enable_expansion_for_user_history_predictor = true;
79 }
80 
~BasePredictor()81 BasePredictor::~BasePredictor() {}
82 
Finish(const ConversionRequest & request,Segments * segments)83 void BasePredictor::Finish(const ConversionRequest &request,
84                            Segments *segments) {
85   user_history_predictor_->Finish(request, segments);
86   dictionary_predictor_->Finish(request, segments);
87 
88   if (segments->conversion_segments_size() < 1 ||
89       segments->request_type() == Segments::CONVERSION) {
90     return;
91   }
92   Segment *segment = segments->mutable_conversion_segment(0);
93   if (segment->candidates_size() < 1) {
94     return;
95   }
96   // update the key as the original key only contains
97   // the 'prefix'.
98   // note that candidate key may be different from request key (=segment key)
99   // due to suggestion/prediction.
100   segment->set_key(segment->candidate(0).key);
101 }
102 
103 // Since DictionaryPredictor is immutable, no need
104 // to call DictionaryPredictor::Revert/Clear*/Finish methods.
Revert(Segments * segments)105 void BasePredictor::Revert(Segments *segments) {
106   user_history_predictor_->Revert(segments);
107 }
108 
ClearAllHistory()109 bool BasePredictor::ClearAllHistory() {
110   return user_history_predictor_->ClearAllHistory();
111 }
112 
ClearUnusedHistory()113 bool BasePredictor::ClearUnusedHistory() {
114   return user_history_predictor_->ClearUnusedHistory();
115 }
116 
ClearHistoryEntry(const string & key,const string & value)117 bool BasePredictor::ClearHistoryEntry(const string &key, const string &value) {
118   return user_history_predictor_->ClearHistoryEntry(key, value);
119 }
120 
Wait()121 bool BasePredictor::Wait() {
122   return user_history_predictor_->Wait();
123 }
124 
Sync()125 bool BasePredictor::Sync() {
126   return user_history_predictor_->Sync();
127 }
128 
Reload()129 bool BasePredictor::Reload() {
130   return user_history_predictor_->Reload();
131 }
132 
133 // static
CreateDefaultPredictor(PredictorInterface * dictionary_predictor,PredictorInterface * user_history_predictor)134 PredictorInterface *DefaultPredictor::CreateDefaultPredictor(
135     PredictorInterface *dictionary_predictor,
136     PredictorInterface *user_history_predictor) {
137   return new DefaultPredictor(dictionary_predictor, user_history_predictor);
138 }
139 
DefaultPredictor(PredictorInterface * dictionary_predictor,PredictorInterface * user_history_predictor)140 DefaultPredictor::DefaultPredictor(PredictorInterface *dictionary_predictor,
141                                    PredictorInterface *user_history_predictor)
142     : BasePredictor(dictionary_predictor, user_history_predictor),
143       empty_request_(),
144       predictor_name_("DefaultPredictor") {}
145 
~DefaultPredictor()146 DefaultPredictor::~DefaultPredictor() {}
147 
PredictForRequest(const ConversionRequest & request,Segments * segments) const148 bool DefaultPredictor::PredictForRequest(const ConversionRequest &request,
149                                          Segments *segments) const {
150   DCHECK(segments->request_type() == Segments::PREDICTION ||
151          segments->request_type() == Segments::SUGGESTION ||
152          segments->request_type() == Segments::PARTIAL_PREDICTION ||
153          segments->request_type() == Segments::PARTIAL_SUGGESTION);
154 
155   if (request.config().presentation_mode()) {
156     return false;
157   }
158 
159   int size = kPredictionSize;
160   if (segments->request_type() == Segments::SUGGESTION) {
161     size = std::min(
162         9, std::max(1, static_cast<int>(request.config().suggestions_size())));
163   }
164 
165   bool result = false;
166   int remained_size = size;
167   segments->set_max_prediction_candidates_size(static_cast<size_t>(size));
168   result |= user_history_predictor_->PredictForRequest(request, segments);
169   remained_size = size - static_cast<size_t>(GetCandidatesSize(*segments));
170 
171   // Do not call dictionary_predictor if the size of candidates get
172   // >= suggestions_size.
173   if (remained_size <= 0) {
174     return result;
175   }
176 
177   segments->set_max_prediction_candidates_size(remained_size);
178   result |= dictionary_predictor_->PredictForRequest(request, segments);
179   remained_size = size - static_cast<size_t>(GetCandidatesSize(*segments));
180 
181   // Do not call extra_predictor if the size of candidates get
182   // >= suggestions_size.
183   if (remained_size <= 0) {
184     return result;
185   }
186 
187   return result;
188 }
189 
190 // static
CreateMobilePredictor(PredictorInterface * dictionary_predictor,PredictorInterface * user_history_predictor)191 PredictorInterface *MobilePredictor::CreateMobilePredictor(
192     PredictorInterface *dictionary_predictor,
193     PredictorInterface *user_history_predictor) {
194   return new MobilePredictor(dictionary_predictor, user_history_predictor);
195 }
196 
MobilePredictor(PredictorInterface * dictionary_predictor,PredictorInterface * user_history_predictor)197 MobilePredictor::MobilePredictor(PredictorInterface *dictionary_predictor,
198                                  PredictorInterface *user_history_predictor)
199     : BasePredictor(dictionary_predictor, user_history_predictor),
200       empty_request_(),
201       predictor_name_("MobilePredictor") {}
202 
~MobilePredictor()203 MobilePredictor::~MobilePredictor() {}
204 
PredictForRequest(const ConversionRequest & request,Segments * segments) const205 bool MobilePredictor::PredictForRequest(const ConversionRequest &request,
206                                         Segments *segments) const {
207   DCHECK(segments->request_type() == Segments::PREDICTION ||
208          segments->request_type() == Segments::SUGGESTION ||
209          segments->request_type() == Segments::PARTIAL_PREDICTION ||
210          segments->request_type() == Segments::PARTIAL_SUGGESTION);
211 
212   if (request.config().presentation_mode()) {
213     return false;
214   }
215 
216   DCHECK(segments);
217 
218   bool result = false;
219   size_t size = 0;
220   size_t history_suggestion_size = IsZeroQuery(request) ? 3 : 2;
221 
222   // TODO(taku,toshiyuki): Must rewrite the logic.
223   switch (segments->request_type()) {
224     case Segments::SUGGESTION: {
225       // Suggestion is triggered at every character insertion.
226       // So here we should use slow predictors.
227       size = GetCandidatesSize(*segments) + history_suggestion_size;
228       segments->set_max_prediction_candidates_size(size);
229       result |= user_history_predictor_->PredictForRequest(request, segments);
230 
231       size = GetCandidatesSize(*segments) + 20;
232       segments->set_max_prediction_candidates_size(size);
233       result |= dictionary_predictor_->PredictForRequest(request, segments);
234 
235       break;
236     }
237     case Segments::PARTIAL_SUGGESTION: {
238       // PARTIAL SUGGESTION can be triggered in a similar manner to that of
239       // SUGGESTION. We don't call slow predictors by the same reason.
240       size = GetCandidatesSize(*segments) + 20;
241       segments->set_max_prediction_candidates_size(size);
242       result |= dictionary_predictor_->PredictForRequest(request, segments);
243 
244       break;
245     }
246     case Segments::PARTIAL_PREDICTION: {
247       segments->set_max_prediction_candidates_size(kMobilePredictionSize);
248       result |= dictionary_predictor_->PredictForRequest(request, segments);
249       break;
250     }
251     case Segments::PREDICTION: {
252       size = GetCandidatesSize(*segments) + history_suggestion_size;
253       segments->set_max_prediction_candidates_size(size);
254       result |= user_history_predictor_->PredictForRequest(request, segments);
255 
256       segments->set_max_prediction_candidates_size(kMobilePredictionSize);
257       result |= dictionary_predictor_->PredictForRequest(request, segments);
258       break;
259     }
260     default: {
261     }  // Never reach here
262   }
263 
264   return result;
265 }
266 
267 }  // namespace mozc
268