Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2)

Side by Side Diff: ui/app_list/search/mixer.cc

Issue 1113483002: App launcher: Added Finch experiment for the search ranking algorithm. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@applist-mixer-debug-framework
Patch Set: Change to a field trial (existing behaviour preserved by default). Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/app_list/search/mixer.h ('k') | ui/app_list/search/mixer_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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>
11 #include <vector> 11 #include <vector>
12 12
13 #include "base/metrics/field_trial.h"
13 #include "ui/app_list/search_provider.h" 14 #include "ui/app_list/search_provider.h"
14 #include "ui/app_list/search_result.h" 15 #include "ui/app_list/search_result.h"
15 16
16 namespace app_list { 17 namespace app_list {
17 18
18 namespace { 19 namespace {
19 20
20 // Maximum number of results to show. 21 // Maximum number of results to show.
22 // NOTE: If the AppListMixer field trial is "Blended", this is actually the
23 // minimum number of results to show. If this quota is not reached, the
24 // per-group limitations are removed and we try again. (We may still not reach
25 // the minumum, but at least we tried.)
21 const size_t kMaxResults = 6; 26 const size_t kMaxResults = 6;
calamity 2015/05/13 03:17:23 I would just break out the semantic concept into a
Matt Giuca 2015/05/13 04:02:00 Done.
22 27
28 const char kAppListMixerFieldTrialName[] = "AppListMixer";
29 const char kAppListMixerFieldTrialEnabled[] = "Blended";
30
23 void UpdateResult(const SearchResult& source, SearchResult* target) { 31 void UpdateResult(const SearchResult& source, SearchResult* target) {
24 target->set_display_type(source.display_type()); 32 target->set_display_type(source.display_type());
25 target->set_title(source.title()); 33 target->set_title(source.title());
26 target->set_title_tags(source.title_tags()); 34 target->set_title_tags(source.title_tags());
27 target->set_details(source.details()); 35 target->set_details(source.details());
28 target->set_details_tags(source.details_tags()); 36 target->set_details_tags(source.details_tags());
29 } 37 }
30 38
39 // Returns true if the "AppListMixer" trial is set to "Blended". This is an
40 // experiment on the new Mixer logic that allows results from different groups
41 // to be blended together, rather than stratified.
42 bool IsBlendedMixerTrialEnabled() {
43 const std::string group_name =
44 base::FieldTrialList::FindFullName(kAppListMixerFieldTrialName);
45 return group_name == kAppListMixerFieldTrialEnabled;
46 }
47
31 } // namespace 48 } // namespace
32 49
33 Mixer::SortData::SortData() : result(NULL), score(0.0) { 50 Mixer::SortData::SortData() : result(NULL), score(0.0) {
34 } 51 }
35 52
36 Mixer::SortData::SortData(SearchResult* result, double score) 53 Mixer::SortData::SortData(SearchResult* result, double score)
37 : result(result), score(score) { 54 : result(result), score(score) {
38 } 55 }
39 56
40 bool Mixer::SortData::operator<(const SortData& other) const { 57 bool Mixer::SortData::operator<(const SortData& other) const {
41 // This data precedes (less than) |other| if it has higher score. 58 // This data precedes (less than) |other| if it has higher score.
42 return score > other.score; 59 return score > other.score;
43 } 60 }
44 61
45 // Used to group relevant providers together for mixing their results. 62 // Used to group relevant providers together for mixing their results.
46 class Mixer::Group { 63 class Mixer::Group {
47 public: 64 public:
48 Group(size_t max_results, double boost) 65 Group(size_t max_results, double boost, double multiplier)
49 : max_results_(max_results), boost_(boost) {} 66 : max_results_(max_results), boost_(boost), multiplier_(multiplier) {}
50 ~Group() {} 67 ~Group() {}
51 68
52 void AddProvider(SearchProvider* provider) { providers_.push_back(provider); } 69 void AddProvider(SearchProvider* provider) { providers_.push_back(provider); }
53 70
54 void FetchResults(bool is_voice_query, const KnownResults& known_results) { 71 void FetchResults(bool is_voice_query, const KnownResults& known_results) {
55 results_.clear(); 72 results_.clear();
56 73
57 for (const SearchProvider* provider : providers_) { 74 for (const SearchProvider* provider : providers_) {
58 for (SearchResult* result : provider->results()) { 75 for (SearchResult* result : provider->results()) {
59 DCHECK(!result->id().empty()); 76 DCHECK(!result->id().empty());
60 77
61 // We cannot rely on providers to give relevance scores in the range 78 // We cannot rely on providers to give relevance scores in the range
62 // [0.0, 1.0] (e.g., PeopleProvider directly gives values from the 79 // [0.0, 1.0] (e.g., PeopleProvider directly gives values from the
63 // Google+ API). Clamp to that range. 80 // Google+ API). Clamp to that range.
64 double relevance = std::min(std::max(result->relevance(), 0.0), 1.0); 81 double relevance = std::min(std::max(result->relevance(), 0.0), 1.0);
65 82
83 double multiplier = multiplier_;
66 double boost = boost_; 84 double boost = boost_;
67 KnownResults::const_iterator known_it = 85 KnownResults::const_iterator known_it =
68 known_results.find(result->id()); 86 known_results.find(result->id());
69 if (known_it != known_results.end()) { 87 if (known_it != known_results.end()) {
70 switch (known_it->second) { 88 switch (known_it->second) {
71 case PERFECT_PRIMARY: 89 case PERFECT_PRIMARY:
72 boost = 4.0; 90 boost = 4.0;
73 break; 91 break;
74 case PREFIX_PRIMARY: 92 case PREFIX_PRIMARY:
75 boost = 3.75; 93 boost = 3.75;
76 break; 94 break;
77 case PERFECT_SECONDARY: 95 case PERFECT_SECONDARY:
78 boost = 3.25; 96 boost = 3.25;
79 break; 97 break;
80 case PREFIX_SECONDARY: 98 case PREFIX_SECONDARY:
81 boost = 3.0; 99 boost = 3.0;
82 break; 100 break;
83 case UNKNOWN_RESULT: 101 case UNKNOWN_RESULT:
84 NOTREACHED() << "Unknown result in KnownResults?"; 102 NOTREACHED() << "Unknown result in KnownResults?";
85 break; 103 break;
86 } 104 }
87 } 105 }
88 106
89 // If this is a voice query, voice results receive a massive boost. 107 // If this is a voice query, voice results receive a massive boost.
90 if (is_voice_query && result->voice_result()) 108 if (is_voice_query && result->voice_result())
91 boost += 4.0; 109 boost += 4.0;
92 110
93 results_.push_back(SortData(result, relevance + boost)); 111 results_.push_back(SortData(result, relevance * multiplier + boost));
94 } 112 }
95 } 113 }
96 114
97 std::sort(results_.begin(), results_.end()); 115 std::sort(results_.begin(), results_.end());
98 } 116 }
99 117
100 const SortedResults& results() const { return results_; } 118 const SortedResults& results() const { return results_; }
101 119
102 const size_t max_results() const { return max_results_; } 120 const size_t max_results() const { return max_results_; }
103 121
104 private: 122 private:
105 typedef std::vector<SearchProvider*> Providers; 123 typedef std::vector<SearchProvider*> Providers;
106 const size_t max_results_; 124 const size_t max_results_;
107 const double boost_; 125 const double boost_;
126 const double multiplier_;
108 127
109 Providers providers_; // Not owned. 128 Providers providers_; // Not owned.
110 SortedResults results_; 129 SortedResults results_;
111 130
112 DISALLOW_COPY_AND_ASSIGN(Group); 131 DISALLOW_COPY_AND_ASSIGN(Group);
113 }; 132 };
114 133
115 Mixer::Mixer(AppListModel::SearchResults* ui_results) 134 Mixer::Mixer(AppListModel::SearchResults* ui_results)
116 : ui_results_(ui_results) { 135 : ui_results_(ui_results) {
117 } 136 }
118 Mixer::~Mixer() { 137 Mixer::~Mixer() {
119 } 138 }
120 139
121 size_t Mixer::AddGroup(size_t max_results, double boost) { 140 size_t Mixer::AddGroup(size_t max_results, double boost, double multiplier) {
122 groups_.push_back(new Group(max_results, boost)); 141 // Only consider |boost| if the AppListMixer field trial is default.
142 // Only consider |multiplier| if the AppListMixer field trial is "Blended".
143 if (IsBlendedMixerTrialEnabled())
144 boost = 0.0;
145 else
146 multiplier = 1.0;
147 groups_.push_back(new Group(max_results, boost, multiplier));
123 return groups_.size() - 1; 148 return groups_.size() - 1;
124 } 149 }
125 150
126 size_t Mixer::AddOmniboxGroup(size_t max_results, double boost) { 151 size_t Mixer::AddOmniboxGroup(size_t max_results,
152 double boost,
153 double multiplier) {
127 // There should not already be an omnibox group. 154 // There should not already be an omnibox group.
128 DCHECK(!has_omnibox_group_); 155 DCHECK(!has_omnibox_group_);
129 size_t id = AddGroup(max_results, boost); 156 size_t id = AddGroup(max_results, boost, multiplier);
130 omnibox_group_ = id; 157 omnibox_group_ = id;
131 has_omnibox_group_ = true; 158 has_omnibox_group_ = true;
132 return id; 159 return id;
133 } 160 }
134 161
135 void Mixer::AddProviderToGroup(size_t group_id, SearchProvider* provider) { 162 void Mixer::AddProviderToGroup(size_t group_id, SearchProvider* provider) {
136 groups_[group_id]->AddProvider(provider); 163 groups_[group_id]->AddProvider(provider);
137 } 164 }
138 165
139 void Mixer::MixAndPublish(bool is_voice_query, 166 void Mixer::MixAndPublish(bool is_voice_query,
140 const KnownResults& known_results) { 167 const KnownResults& known_results) {
141 FetchResults(is_voice_query, known_results); 168 FetchResults(is_voice_query, known_results);
142 169
143 SortedResults results; 170 SortedResults results;
144 results.reserve(kMaxResults); 171 results.reserve(kMaxResults);
145 172
146 // Add results from non-omnibox groups first. Limit to the maximum number of 173 if (IsBlendedMixerTrialEnabled()) {
calamity 2015/05/13 03:17:23 These 2 blocks are a bit huge, consider breaking t
Matt Giuca 2015/05/13 04:02:00 I think this will just create more churn now, and
calamity 2015/05/13 05:12:25 Alright.
147 // results in each group. 174 // Add results from each group. Limit to the maximum number of results in
148 for (size_t i = 0; i < groups_.size(); ++i) { 175 // each group.
149 if (!has_omnibox_group_ || i != omnibox_group_) { 176 for (const Group* group : groups_) {
150 const Group& group = *groups_[i];
151 size_t num_results = 177 size_t num_results =
152 std::min(group.results().size(), group.max_results()); 178 std::min(group->results().size(), group->max_results());
153 results.insert(results.end(), group.results().begin(), 179 results.insert(results.end(), group->results().begin(),
154 group.results().begin() + num_results); 180 group->results().begin() + num_results);
155 } 181 }
182 // Remove results with duplicate IDs before sorting. If two providers give a
183 // result with the same ID, the result from the provider with the *lower
184 // group number* will be kept (e.g., an app result takes priority over a web
185 // store result with the same ID).
186 RemoveDuplicates(&results);
187 std::sort(results.begin(), results.end());
188
189 if (results.size() < kMaxResults) {
190 size_t original_size = results.size();
191 // We didn't get enough results. Insert all the results again, and this
192 // time, do not limit the maximum number of results from each group. (This
193 // will result in duplicates, which will be removed by RemoveDuplicates.)
194 for (const Group* group : groups_) {
195 results.insert(results.end(), group->results().begin(),
196 group->results().end());
197 }
198 RemoveDuplicates(&results);
199 // Sort just the newly added results. This ensures that, for example, if
200 // there are 6 Omnibox results (score = 0.8) and 1 People result (score =
201 // 0.4) that the People result will be 5th, not 7th, because the Omnibox
202 // group has a soft maximum of 4 results. (Otherwise, the People result
203 // would not be seen at all once the result list is truncated.)
204 std::sort(results.begin() + original_size, results.end());
205 }
206 } else {
207 // Add results from non-omnibox groups first. Limit to the maximum number of
208 // results in each group.
209 for (size_t i = 0; i < groups_.size(); ++i) {
210 if (!has_omnibox_group_ || i != omnibox_group_) {
211 const Group& group = *groups_[i];
212 size_t num_results =
213 std::min(group.results().size(), group.max_results());
214 results.insert(results.end(), group.results().begin(),
215 group.results().begin() + num_results);
216 }
217 }
218
219 // Collapse duplicate apps from local and web store.
220 RemoveDuplicates(&results);
221
222 // Fill the remaining slots with omnibox results. Always add at least one
223 // omnibox result (even if there are no more slots; if we over-fill the
224 // vector, the web store and people results will be removed in a later
225 // step). Note: max_results() is ignored for the omnibox group.
226 if (has_omnibox_group_) {
227 CHECK_LT(omnibox_group_, groups_.size());
228 const Group& omnibox_group = *groups_[omnibox_group_];
229 const size_t omnibox_results = std::min(
230 omnibox_group.results().size(),
231 results.size() < kMaxResults ? kMaxResults - results.size() : 1);
232 results.insert(results.end(), omnibox_group.results().begin(),
233 omnibox_group.results().begin() + omnibox_results);
234 }
235
236 std::sort(results.begin(), results.end());
237 RemoveDuplicates(&results);
238 if (results.size() > kMaxResults)
239 results.resize(kMaxResults);
156 } 240 }
157 241
158 // Collapse duplicate apps from local and web store.
159 RemoveDuplicates(&results);
160
161 // Fill the remaining slots with omnibox results. Always add at least one
162 // omnibox result (even if there are no more slots; if we over-fill the
163 // vector, the web store and people results will be removed in a later step).
164 // Note: max_results() is ignored for the omnibox group.
165 if (has_omnibox_group_) {
166 CHECK_LT(omnibox_group_, groups_.size());
167 const Group& omnibox_group = *groups_[omnibox_group_];
168 const size_t omnibox_results = std::min(
169 omnibox_group.results().size(),
170 results.size() < kMaxResults ? kMaxResults - results.size() : 1);
171 results.insert(results.end(), omnibox_group.results().begin(),
172 omnibox_group.results().begin() + omnibox_results);
173 }
174
175 std::sort(results.begin(), results.end());
176 RemoveDuplicates(&results);
177 if (results.size() > kMaxResults)
178 results.resize(kMaxResults);
179
180 Publish(results, ui_results_); 242 Publish(results, ui_results_);
181 } 243 }
182 244
183 void Mixer::Publish(const SortedResults& new_results, 245 void Mixer::Publish(const SortedResults& new_results,
184 AppListModel::SearchResults* ui_results) { 246 AppListModel::SearchResults* ui_results) {
185 typedef std::map<std::string, SearchResult*> IdToResultMap; 247 typedef std::map<std::string, SearchResult*> IdToResultMap;
186 248
187 // The following algorithm is used: 249 // The following algorithm is used:
188 // 1. Transform the |ui_results| list into an unordered map from result ID 250 // 1. Transform the |ui_results| list into an unordered map from result ID
189 // to item. 251 // to item.
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
248 results->swap(final); 310 results->swap(final);
249 } 311 }
250 312
251 void Mixer::FetchResults(bool is_voice_query, 313 void Mixer::FetchResults(bool is_voice_query,
252 const KnownResults& known_results) { 314 const KnownResults& known_results) {
253 for (auto* group : groups_) 315 for (auto* group : groups_)
254 group->FetchResults(is_voice_query, known_results); 316 group->FetchResults(is_voice_query, known_results);
255 } 317 }
256 318
257 } // namespace app_list 319 } // namespace app_list
OLDNEW
« no previous file with comments | « ui/app_list/search/mixer.h ('k') | ui/app_list/search/mixer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698