OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/app_list/search/mixer.h" | 5 #include "ui/app_list/search/mixer.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <map> | 8 #include <map> |
9 #include <set> | 9 #include <set> |
10 #include <string> | 10 #include <string> |
(...skipping 10 matching lines...) Expand all Loading... |
21 void UpdateResult(const SearchResult& source, SearchResult* target) { | 21 void UpdateResult(const SearchResult& source, SearchResult* target) { |
22 target->set_display_type(source.display_type()); | 22 target->set_display_type(source.display_type()); |
23 target->set_title(source.title()); | 23 target->set_title(source.title()); |
24 target->set_title_tags(source.title_tags()); | 24 target->set_title_tags(source.title_tags()); |
25 target->set_details(source.details()); | 25 target->set_details(source.details()); |
26 target->set_details_tags(source.details_tags()); | 26 target->set_details_tags(source.details_tags()); |
27 } | 27 } |
28 | 28 |
29 } // namespace | 29 } // namespace |
30 | 30 |
31 Mixer::SortData::SortData() : result(NULL), score(0.0) { | 31 Mixer::SortData::SortData() : result(nullptr), score(0.0) {} |
32 } | |
33 | 32 |
34 Mixer::SortData::SortData(SearchResult* result, double score) | 33 Mixer::SortData::SortData(SearchResult* result, double score) |
35 : result(result), score(score) { | 34 : result(result), score(score) { |
36 } | 35 } |
37 | 36 |
38 bool Mixer::SortData::operator<(const SortData& other) const { | 37 bool Mixer::SortData::operator<(const SortData& other) const { |
39 // This data precedes (less than) |other| if it has higher score. | 38 // This data precedes (less than) |other| if it has higher score. |
40 return score > other.score; | 39 return score > other.score; |
41 } | 40 } |
42 | 41 |
43 // Used to group relevant providers together for mixing their results. | 42 // Used to group relevant providers together for mixing their results. |
44 class Mixer::Group { | 43 class Mixer::Group { |
45 public: | 44 public: |
46 Group(size_t max_results, double multiplier) | 45 Group(size_t max_results, double multiplier) |
47 : max_results_(max_results), multiplier_(multiplier) {} | 46 : max_results_(max_results), multiplier_(multiplier) {} |
48 ~Group() {} | 47 ~Group() {} |
49 | 48 |
50 void AddProvider(SearchProvider* provider) { providers_.push_back(provider); } | 49 void AddProvider(SearchProvider* provider) { |
| 50 providers_.emplace_back(provider); |
| 51 } |
51 | 52 |
52 void FetchResults(bool is_voice_query, const KnownResults& known_results) { | 53 void FetchResults(bool is_voice_query, const KnownResults& known_results) { |
53 results_.clear(); | 54 results_.clear(); |
54 | 55 |
55 for (const SearchProvider* provider : providers_) { | 56 for (const SearchProvider* provider : providers_) { |
56 for (SearchResult* result : provider->results()) { | 57 for (const auto& result : provider->results()) { |
57 DCHECK(!result->id().empty()); | 58 DCHECK(!result->id().empty()); |
58 | 59 |
59 // We cannot rely on providers to give relevance scores in the range | 60 // We cannot rely on providers to give relevance scores in the range |
60 // [0.0, 1.0] (e.g., PeopleProvider directly gives values from the | 61 // [0.0, 1.0] (e.g., PeopleProvider directly gives values from the |
61 // Google+ API). Clamp to that range. | 62 // Google+ API). Clamp to that range. |
62 double relevance = std::min(std::max(result->relevance(), 0.0), 1.0); | 63 const double relevance = |
63 | 64 std::min(std::max(result->relevance(), 0.0), 1.0); |
64 double multiplier = multiplier_; | |
65 double boost = 0.0; | 65 double boost = 0.0; |
66 | 66 |
67 // Recommendations should not be affected by query-to-launch correlation | 67 // Recommendations should not be affected by query-to-launch correlation |
68 // from KnownResults as it causes recommendations to become dominated by | 68 // from KnownResults as it causes recommendations to become dominated by |
69 // previously clicked results. This happens because the recommendation | 69 // previously clicked results. This happens because the recommendation |
70 // query is the empty string and the clicked results get forever | 70 // query is the empty string and the clicked results get forever |
71 // boosted. | 71 // boosted. |
72 if (result->display_type() != SearchResult::DISPLAY_RECOMMENDATION) { | 72 if (result->display_type() != SearchResult::DISPLAY_RECOMMENDATION) { |
73 KnownResults::const_iterator known_it = | 73 KnownResults::const_iterator known_it = |
74 known_results.find(result->id()); | 74 known_results.find(result->id()); |
(...skipping 15 matching lines...) Expand all Loading... |
90 NOTREACHED() << "Unknown result in KnownResults?"; | 90 NOTREACHED() << "Unknown result in KnownResults?"; |
91 break; | 91 break; |
92 } | 92 } |
93 } | 93 } |
94 | 94 |
95 // If this is a voice query, voice results receive a massive boost. | 95 // If this is a voice query, voice results receive a massive boost. |
96 if (is_voice_query && result->voice_result()) | 96 if (is_voice_query && result->voice_result()) |
97 boost += 4.0; | 97 boost += 4.0; |
98 } | 98 } |
99 | 99 |
100 results_.push_back(SortData(result, relevance * multiplier + boost)); | 100 results_.emplace_back(result.get(), relevance * multiplier_ + boost); |
101 } | 101 } |
102 } | 102 } |
103 | 103 |
104 std::sort(results_.begin(), results_.end()); | 104 std::sort(results_.begin(), results_.end()); |
105 } | 105 } |
106 | 106 |
107 const SortedResults& results() const { return results_; } | 107 const SortedResults& results() const { return results_; } |
108 | 108 |
109 size_t max_results() const { return max_results_; } | 109 size_t max_results() const { return max_results_; } |
110 | 110 |
(...skipping 27 matching lines...) Expand all Loading... |
138 const KnownResults& known_results, | 138 const KnownResults& known_results, |
139 size_t num_max_results) { | 139 size_t num_max_results) { |
140 FetchResults(is_voice_query, known_results); | 140 FetchResults(is_voice_query, known_results); |
141 | 141 |
142 SortedResults results; | 142 SortedResults results; |
143 results.reserve(num_max_results); | 143 results.reserve(num_max_results); |
144 | 144 |
145 // Add results from each group. Limit to the maximum number of results in each | 145 // Add results from each group. Limit to the maximum number of results in each |
146 // group. | 146 // group. |
147 for (const Group* group : groups_) { | 147 for (const Group* group : groups_) { |
148 size_t num_results = | 148 const size_t num_results = |
149 std::min(group->results().size(), group->max_results()); | 149 std::min(group->results().size(), group->max_results()); |
150 results.insert(results.end(), group->results().begin(), | 150 results.insert(results.end(), group->results().begin(), |
151 group->results().begin() + num_results); | 151 group->results().begin() + num_results); |
152 } | 152 } |
153 // Remove results with duplicate IDs before sorting. If two providers give a | 153 // Remove results with duplicate IDs before sorting. If two providers give a |
154 // result with the same ID, the result from the provider with the *lower group | 154 // result with the same ID, the result from the provider with the *lower group |
155 // number* will be kept (e.g., an app result takes priority over a web store | 155 // number* will be kept (e.g., an app result takes priority over a web store |
156 // result with the same ID). | 156 // result with the same ID). |
157 RemoveDuplicates(&results); | 157 RemoveDuplicates(&results); |
158 std::sort(results.begin(), results.end()); | 158 std::sort(results.begin(), results.end()); |
159 | 159 |
160 if (results.size() < num_max_results) { | 160 const size_t original_size = results.size(); |
161 size_t original_size = results.size(); | 161 if (original_size < num_max_results) { |
162 // We didn't get enough results. Insert all the results again, and this | 162 // We didn't get enough results. Insert all the results again, and this |
163 // time, do not limit the maximum number of results from each group. (This | 163 // time, do not limit the maximum number of results from each group. (This |
164 // will result in duplicates, which will be removed by RemoveDuplicates.) | 164 // will result in duplicates, which will be removed by RemoveDuplicates.) |
165 for (const Group* group : groups_) { | 165 for (const Group* group : groups_) { |
166 results.insert(results.end(), group->results().begin(), | 166 results.insert(results.end(), group->results().begin(), |
167 group->results().end()); | 167 group->results().end()); |
168 } | 168 } |
169 RemoveDuplicates(&results); | 169 RemoveDuplicates(&results); |
170 // Sort just the newly added results. This ensures that, for example, if | 170 // Sort just the newly added results. This ensures that, for example, if |
171 // there are 6 Omnibox results (score = 0.8) and 1 People result (score = | 171 // there are 6 Omnibox results (score = 0.8) and 1 People result (score = |
(...skipping 13 matching lines...) Expand all Loading... |
185 // to item. | 185 // to item. |
186 // 2. Use the order of items in |new_results| to build an ordered list. If the | 186 // 2. Use the order of items in |new_results| to build an ordered list. If the |
187 // result IDs exist in the map, update and use the item in the map and delete | 187 // result IDs exist in the map, update and use the item in the map and delete |
188 // it from the map afterwards. Otherwise, clone new items from |new_results|. | 188 // it from the map afterwards. Otherwise, clone new items from |new_results|. |
189 // 3. Delete the objects remaining in the map as they are unused. | 189 // 3. Delete the objects remaining in the map as they are unused. |
190 | 190 |
191 // We have to erase all results at once so that observers are notified with | 191 // We have to erase all results at once so that observers are notified with |
192 // meaningful indexes. | 192 // meaningful indexes. |
193 auto current_results = ui_results->RemoveAll(); | 193 auto current_results = ui_results->RemoveAll(); |
194 std::map<std::string, std::unique_ptr<SearchResult>> ui_results_map; | 194 std::map<std::string, std::unique_ptr<SearchResult>> ui_results_map; |
195 for (std::unique_ptr<SearchResult>& ui_result : current_results) | 195 for (auto& ui_result : current_results) |
196 ui_results_map[ui_result->id()] = std::move(ui_result); | 196 ui_results_map[ui_result->id()] = std::move(ui_result); |
197 | 197 |
198 // Add items back to |ui_results| in the order of |new_results|. | 198 // Add items back to |ui_results| in the order of |new_results|. |
199 for (const SortData& sort_data : new_results) { | 199 for (const SortData& sort_data : new_results) { |
200 const SearchResult& new_result = *sort_data.result; | 200 const SearchResult& new_result = *sort_data.result; |
201 auto ui_result_it = ui_results_map.find(new_result.id()); | 201 auto ui_result_it = ui_results_map.find(new_result.id()); |
202 if (ui_result_it != ui_results_map.end()) { | 202 if (ui_result_it != ui_results_map.end()) { |
203 // Update and use the old result if it exists. | 203 // Update and use the old result if it exists. |
204 std::unique_ptr<SearchResult> ui_result = std::move(ui_result_it->second); | 204 std::unique_ptr<SearchResult> ui_result = std::move(ui_result_it->second); |
205 UpdateResult(new_result, ui_result.get()); | 205 UpdateResult(new_result, ui_result.get()); |
(...skipping 14 matching lines...) Expand all Loading... |
220 | 220 |
221 // Any remaining results in |ui_results_map| will be automatically deleted. | 221 // Any remaining results in |ui_results_map| will be automatically deleted. |
222 } | 222 } |
223 | 223 |
224 void Mixer::RemoveDuplicates(SortedResults* results) { | 224 void Mixer::RemoveDuplicates(SortedResults* results) { |
225 SortedResults final; | 225 SortedResults final; |
226 final.reserve(results->size()); | 226 final.reserve(results->size()); |
227 | 227 |
228 std::set<std::string> id_set; | 228 std::set<std::string> id_set; |
229 for (const SortData& sort_data : *results) { | 229 for (const SortData& sort_data : *results) { |
230 const std::string& id = sort_data.result->id(); | 230 if (!id_set.insert(sort_data.result->id()).second) |
231 if (id_set.find(id) != id_set.end()) | |
232 continue; | 231 continue; |
233 | 232 |
234 id_set.insert(id); | 233 final.emplace_back(sort_data); |
235 final.push_back(sort_data); | |
236 } | 234 } |
237 | 235 |
238 results->swap(final); | 236 results->swap(final); |
239 } | 237 } |
240 | 238 |
241 void Mixer::FetchResults(bool is_voice_query, | 239 void Mixer::FetchResults(bool is_voice_query, |
242 const KnownResults& known_results) { | 240 const KnownResults& known_results) { |
243 for (auto* group : groups_) | 241 for (auto* group : groups_) |
244 group->FetchResults(is_voice_query, known_results); | 242 group->FetchResults(is_voice_query, known_results); |
245 } | 243 } |
246 | 244 |
247 } // namespace app_list | 245 } // namespace app_list |
OLD | NEW |