Skip to content

Commit 2c9a557

Browse files
authored
Merge description + readme token index. (dart-lang#9446)
1 parent b3967ae commit 2c9a557

5 files changed

Lines changed: 77 additions & 32 deletions

File tree

app/lib/search/mem_index.dart

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ class InMemoryPackageIndex {
2626
final List<PackageDocument> _documents;
2727
final _nameToIndex = <String, int>{};
2828
late final PackageNameIndex _packageNameIndex;
29-
late final TokenIndex _descrIndex;
30-
late final TokenIndex _readmeIndex;
29+
late final TokenIndex _textIndex;
3130
late final List<IndexedApiDocPage> _apiDocPageKeys;
3231
late final TokenIndex _apiSymbolIndex;
3332
late final _bitArrayPool = BitArrayPool(_documents.length);
@@ -78,11 +77,13 @@ class InMemoryPackageIndex {
7877

7978
final packageKeys = _documents.map((d) => d.package).toList();
8079
_packageNameIndex = PackageNameIndex(packageKeys);
81-
_descrIndex = TokenIndex(
82-
_documents.map((d) => d.description).toList(),
83-
skipDocumentWeight: true,
84-
);
85-
_readmeIndex = TokenIndex(_documents.map((d) => d.readme).toList());
80+
_textIndex = TokenIndex.fromFields(_documents, (doc) {
81+
return [
82+
if (doc.description != null)
83+
TokenIndexField(doc.description!, skipDocumentWeight: true),
84+
if (doc.readme != null) TokenIndexField(doc.readme!, weight: 0.75),
85+
];
86+
});
8687
_apiDocPageKeys = apiDocPageKeys;
8788
_apiSymbolIndex = TokenIndex.fromValues(apiDocPageValues);
8889

@@ -458,8 +459,7 @@ class InMemoryPackageIndex {
458459
return aborted;
459460
}
460461

461-
final matchDescription = textMatchExtent.shouldMatchDescription();
462-
final matchReadme = textMatchExtent.shouldMatchReadme();
462+
final matchText = textMatchExtent.shouldMatchText();
463463
final matchApi = textMatchExtent.shouldMatchApi();
464464

465465
for (final word in words) {
@@ -471,15 +471,8 @@ class InMemoryPackageIndex {
471471
filterOnNonZeros: packageScores,
472472
);
473473

474-
if (matchDescription) {
475-
await _descrIndex.searchAndAccumulate(word, score: wordScore);
476-
}
477-
if (matchReadme) {
478-
await _readmeIndex.searchAndAccumulate(
479-
word,
480-
weight: 0.75,
481-
score: wordScore,
482-
);
474+
if (matchText) {
475+
await _textIndex.searchAndAccumulate(word, score: wordScore);
483476
}
484477
packageScores.multiplyAllFrom(wordScore);
485478
},
@@ -538,8 +531,8 @@ class InMemoryPackageIndex {
538531
final matchedAllPhrases = phrases.every(
539532
(phrase) =>
540533
(matchName && doc.package.contains(phrase)) ||
541-
(matchDescription && doc.description!.contains(phrase)) ||
542-
(matchReadme && doc.readme!.contains(phrase)),
534+
(matchText && doc.description!.contains(phrase)) ||
535+
(matchText && doc.readme!.contains(phrase)),
543536
);
544537
if (!matchedAllPhrases) {
545538
packageScores.setValue(i, 0);

app/lib/search/token_index.dart

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,24 @@ class _PostingsBuilder {
136136
}
137137
}
138138

139+
/// Describes a weighted field of a document.
140+
class TokenIndexField {
141+
/// The text value of the field.
142+
final String text;
143+
144+
/// Whether to skip the normalization for this field.
145+
final bool skipDocumentWeight;
146+
147+
/// Token weight multiplier.
148+
final double weight;
149+
150+
TokenIndexField(
151+
this.text, {
152+
this.skipDocumentWeight = false,
153+
this.weight = 1.0,
154+
});
155+
}
156+
139157
/// Stores a token -> documentId inverted index with weights.
140158
class TokenIndex {
141159
final int _length;
@@ -157,6 +175,39 @@ class TokenIndex {
157175
return TokenIndex._(values.length, _finalize(builders));
158176
}
159177

178+
/// Builds a single index from a list of [objects], extracting one or more
179+
/// weighted text fields from each via [extractFields] (e.g. description +
180+
/// readme of the same package document).
181+
///
182+
/// For each object, the tokens of all its fields are merged, keeping the
183+
/// maximum weight per token.
184+
static TokenIndex fromFields<T>(
185+
List<T> objects,
186+
List<TokenIndexField> Function(T object) extractFields,
187+
) {
188+
final builders = <String, _PostingsBuilder>{};
189+
for (var i = 0; i < objects.length; i++) {
190+
final merged = <String, double>{};
191+
for (final field in extractFields(objects[i])) {
192+
final tokens = tokenize(field.text);
193+
if (tokens == null || tokens.isEmpty) continue;
194+
final dw = field.skipDocumentWeight
195+
? 1.0
196+
: _documentWeight(tokens.length);
197+
for (final e in tokens.entries) {
198+
final w = e.value / dw * field.weight;
199+
final old = merged[e.key];
200+
if (old == null || w > old) {
201+
merged[e.key] = w;
202+
}
203+
}
204+
}
205+
// Weights are already normalized above, so skip the document weight here.
206+
_addTokens(i, merged, true, builders);
207+
}
208+
return TokenIndex._(objects.length, _finalize(builders));
209+
}
210+
160211
factory TokenIndex.fromValues(
161212
List<List<String>?> values, {
162213
bool skipDocumentWeight = false,
@@ -197,7 +248,7 @@ class TokenIndex {
197248
) {
198249
if (tokens == null || tokens.isEmpty) return;
199250
// Document weight is a highly scaled-down proxy of the length.
200-
final dw = skipDocumentWeight ? 1.0 : 1 + math.log(1 + tokens.length) / 100;
251+
final dw = skipDocumentWeight ? 1.0 : _documentWeight(tokens.length);
201252
for (final e in tokens.entries) {
202253
final weight = e.value / dw;
203254
final quantized = (weight * 256 - 1).round().clamp(0, 255);
@@ -207,6 +258,8 @@ class TokenIndex {
207258
}
208259
}
209260

261+
static double _documentWeight(int length) => 1 + math.log(1 + length) / 100;
262+
210263
/// Match the text against the corpus and return the tokens or
211264
/// their partial segments that have match.
212265
@visibleForTesting

app/lib/service/entrypoint/search_index.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,20 +203,16 @@ class LatencyAwareSearchIndex implements SearchIndex {
203203
_logger.info('[text-match-normal]');
204204
return TextMatchExtent.api;
205205
}
206-
if (latency < const Duration(seconds: 2)) {
207-
_logger.info('[text-match-readme]');
208-
return TextMatchExtent.readme;
209-
}
210206
if (latency < const Duration(seconds: 4)) {
211-
_logger.info('[text-match-description]');
212-
return TextMatchExtent.description;
207+
_logger.info('[text-match-text]');
208+
return TextMatchExtent.text;
213209
}
214210
if (latency < const Duration(seconds: 10)) {
215211
_logger.info('[text-match-name]');
216212
return TextMatchExtent.name;
217213
}
218214
// TODO: use `TextMatchExtent.none` after we are confident about this change.
219215
_logger.info('[text-match-none]');
220-
return TextMatchExtent.readme;
216+
return TextMatchExtent.text;
221217
}
222218
}

pkg/_pub_shared/lib/search/search_request_data.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,15 @@ enum TextMatchExtent {
118118
/// Text search is on package names.
119119
name,
120120

121+
/// Text search is on names, descriptions, topic tags and readme content.
122+
text,
123+
121124
/// Text search is on package names, descriptions and topic tags.
125+
@Deprecated('Use `text`')
122126
description,
123127

124128
/// Text search is on names, descriptions, topic tags and readme content.
129+
@Deprecated('Use `text`')
125130
readme,
126131

127132
/// Text search is on names, descriptions, topic tags, readme content and API symbols.
@@ -130,11 +135,8 @@ enum TextMatchExtent {
130135
/// Text search is on package names.
131136
bool shouldMatchName() => index >= name.index;
132137

133-
/// Text search is on package names, descriptions and topic tags.
134-
bool shouldMatchDescription() => index >= description.index;
135-
136138
/// Text search is on names, descriptions, topic tags and readme content.
137-
bool shouldMatchReadme() => index >= readme.index;
139+
bool shouldMatchText() => index >= text.index;
138140

139141
/// Text search is on names, descriptions, topic tags, readme content and API symbols.
140142
bool shouldMatchApi() => index >= api.index;

pkg/_pub_shared/lib/search/search_request_data.g.dart

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)