@@ -71,66 +71,59 @@ class SimplePackageIndex implements PackageIndex {
71
71
72
72
@override
73
73
Future <PackageSearchResult > search (SearchQuery query) async {
74
- final Map <String , double > total = < String , double > {};
75
- void addAll (Map <String , double > scores, double weight) {
76
- scores? .forEach ((String url, double score) {
77
- if (score != null ) {
78
- final double prev = total[url] ?? 0.0 ;
79
- total[url] = prev + score * weight;
80
- }
81
- });
82
- }
83
-
84
- addAll (_nameIndex.search (query.text), 0.70 );
85
- addAll (_descrIndex.search (query.text), 0.10 );
86
- addAll (_readmeIndex.search (query.text), 0.05 );
87
-
88
- if ((query.text == null || query.text.isEmpty) &&
89
- query.packagePrefix != null ) {
90
- addAll (_nameIndex.search (query.packagePrefix), 0.8 );
74
+ // do text matching
75
+ final Score textScore = _searchText (query.text, query.packagePrefix);
76
+
77
+ // The set of urls to filter on.
78
+ final Set <String > urls =
79
+ textScore? .getKeys ()? .toSet () ?? _documents.keys.toSet ();
80
+
81
+ // filter on package prefix
82
+ if (query.packagePrefix != null ) {
83
+ urls.removeWhere (
84
+ (url) => ! _documents[url]
85
+ .package
86
+ .toLowerCase ()
87
+ .startsWith (query.packagePrefix.toLowerCase ()),
88
+ );
91
89
}
92
90
93
- addAll (getHealthScore (total.keys), 0.05 );
94
- addAll (getPopularityScore (total.keys), 0.10 );
95
-
96
- List <PackageScore > results = < PackageScore > [];
97
- for (String url in total.keys) {
98
- final PackageDocument doc = _documents[url];
99
-
100
- // filter on platform
101
- if (query.platformPredicate != null &&
102
- ! query.platformPredicate.matches (doc.platforms)) {
103
- continue ;
104
- }
105
-
106
- // filter on package prefix
107
- if (query.packagePrefix != null &&
108
- ! doc.package
109
- .toLowerCase ()
110
- .startsWith (query.packagePrefix.toLowerCase ())) {
111
- continue ;
112
- }
113
-
114
- results.add (new PackageScore (
115
- url: doc.url,
116
- package: doc.package,
117
- score: total[url],
118
- ));
91
+ // filter on platform
92
+ if (query.platformPredicate != null ) {
93
+ urls.removeWhere (
94
+ (url) => ! query.platformPredicate.matches (_documents[url].platforms));
119
95
}
120
96
121
- results.sort ((a, b) => - a.score.compareTo (b.score));
122
-
123
- // filter out the noise (maybe a single matching ngram)
124
- if (results.isNotEmpty) {
125
- final double bestScore = results.first.score;
126
- final double scoreTreshold = bestScore / 25 ;
127
- results.removeWhere ((pr) => pr.score < scoreTreshold);
97
+ // reduce text results if filter did remove an url
98
+ textScore? .removeWhere ((key) => ! urls.contains (key));
99
+
100
+ List <PackageScore > results;
101
+ switch (query.order ?? SearchOrder .overall) {
102
+ case SearchOrder .overall:
103
+ final Score overallScore = new Score ()
104
+ ..addValues (textScore? .values, 0.85 )
105
+ ..addValues (getPopularityScore (urls), 0.10 )
106
+ ..addValues (getHealthScore (urls), 0.05 );
107
+ results = _rankWithValues (overallScore.values);
108
+ break ;
109
+ case SearchOrder .text:
110
+ results = _rankWithValues (textScore.values);
111
+ break ;
112
+ case SearchOrder .updated:
113
+ results = _rankWithComparator (urls, _compareUpdated);
114
+ break ;
115
+ case SearchOrder .popularity:
116
+ results = _rankWithValues (getPopularityScore (urls));
117
+ break ;
118
+ case SearchOrder .health:
119
+ results = _rankWithValues (getHealthScore (urls));
120
+ break ;
128
121
}
129
122
130
123
// bound by offset and limit
131
- final int totalCount = min (maxSearchResults, results.length) ;
124
+ final int totalCount = results.length;
132
125
if (query.offset != null && query.offset > 0 ) {
133
- if (query.offset > totalCount ) {
126
+ if (query.offset >= results.length ) {
134
127
results = < PackageScore > [];
135
128
} else {
136
129
results = results.sublist (query.offset);
@@ -168,6 +161,81 @@ class SimplePackageIndex implements PackageIndex {
168
161
value: (String url) => _documents[url].popularity * 100 ,
169
162
);
170
163
}
164
+
165
+ Score _searchText (String text, String packagePrefix) {
166
+ if (text != null && text.isNotEmpty) {
167
+ final Score textScore = new Score ()
168
+ ..addValues (_nameIndex.search (text), 0.82 )
169
+ ..addValues (_descrIndex.search (text), 0.12 )
170
+ ..addValues (_readmeIndex.search (text), 0.06 );
171
+ // removes scores that are less than 5% of the best
172
+ textScore.removeLowScores (0.05 );
173
+ // removes scores that are low
174
+ textScore.removeWhere ((url) => textScore.values[url] < 1.0 );
175
+ return textScore;
176
+ }
177
+ return null ;
178
+ }
179
+
180
+ List <PackageScore > _rankWithValues (Map <String , double > values) {
181
+ final List <PackageScore > list = values.keys
182
+ .map ((url) => new PackageScore (
183
+ url: url,
184
+ package: _documents[url].package,
185
+ score: values[url],
186
+ ))
187
+ .toList ();
188
+ list.sort ((a, b) {
189
+ final int scoreCompare = - a.score.compareTo (b.score);
190
+ if (scoreCompare != 0 ) return scoreCompare;
191
+ // if two packages got the same score, order by last updated
192
+ return _compareUpdated (_documents[a.url], _documents[b.url]);
193
+ });
194
+ return list;
195
+ }
196
+
197
+ List <PackageScore > _rankWithComparator (
198
+ Set <String > urls, int compare (PackageDocument a, PackageDocument b)) {
199
+ final List <PackageScore > list = urls
200
+ .map ((url) =>
201
+ new PackageScore (url: url, package: _documents[url].package))
202
+ .toList ();
203
+ list.sort ((a, b) => compare (_documents[a.url], _documents[b.url]));
204
+ return list;
205
+ }
206
+
207
+ int _compareUpdated (PackageDocument a, PackageDocument b) {
208
+ if (a.updated == null ) return - 1 ;
209
+ if (b.updated == null ) return 1 ;
210
+ return - a.updated.compareTo (b.updated);
211
+ }
212
+ }
213
+
214
+ class Score {
215
+ final Map <String , double > values = < String , double > {};
216
+
217
+ Iterable <String > getKeys () => values.keys;
218
+
219
+ void addValues (Map <String , double > newValues, double weight) {
220
+ if (newValues == null ) return ;
221
+ newValues.forEach ((String key, double score) {
222
+ if (score != null ) {
223
+ final double prev = values[key] ?? 0.0 ;
224
+ values[key] = prev + score * weight;
225
+ }
226
+ });
227
+ }
228
+
229
+ void removeWhere (bool keyCondition (String key)) {
230
+ final Set <String > keysToRemove = values.keys.where (keyCondition).toSet ();
231
+ keysToRemove.forEach (values.remove);
232
+ }
233
+
234
+ void removeLowScores (double fraction) {
235
+ final double maxValue = values.values.fold (0.0 , max);
236
+ final double cutoff = maxValue * fraction;
237
+ removeWhere ((key) => values[key] < cutoff);
238
+ }
171
239
}
172
240
173
241
class TokenIndex {
0 commit comments