diff --git a/app/lib/search/mem_index.dart b/app/lib/search/mem_index.dart index 0aabd9af30..2cd6811545 100644 --- a/app/lib/search/mem_index.dart +++ b/app/lib/search/mem_index.dart @@ -113,16 +113,13 @@ class InMemoryPackageIndex { } PackageSearchResult search(ServiceSearchQuery query) { - final packages = Set.of(_documentsByName.keys); + final packageScores = IndexedScore(_packageNameIndex._packageNames, 1.0); // filter on package prefix if (query.parsedQuery.packagePrefix != null) { final String prefix = query.parsedQuery.packagePrefix!.toLowerCase(); - packages.removeWhere( - (package) => !_documentsByName[package]! - .package - .toLowerCase() - .startsWith(prefix), + packageScores.retainWhere( + (i, _) => _documents[i].packageNameLowerCased.startsWith(prefix), ); } @@ -130,14 +127,14 @@ class InMemoryPackageIndex { final combinedTagsPredicate = query.tagsPredicate.appendPredicate(query.parsedQuery.tagsPredicate); if (combinedTagsPredicate.isNotEmpty) { - packages.retainWhere((package) => combinedTagsPredicate - .matches(_documentsByName[package]!.tagsForLookup)); + packageScores.retainWhere( + (i, _) => combinedTagsPredicate.matches(_documents[i].tagsForLookup)); } // filter on dependency if (query.parsedQuery.hasAnyDependency) { - packages.removeWhere((package) { - final doc = _documentsByName[package]!; + packageScores.removeWhere((i, _) { + final doc = _documents[i]; if (doc.dependencies.isEmpty) return true; for (final dependency in query.parsedQuery.allDependencies) { if (!doc.dependencies.containsKey(dependency)) return true; @@ -152,22 +149,18 @@ class InMemoryPackageIndex { // filter on points if (query.minPoints != null && query.minPoints! > 0) { - packages.removeWhere((package) { - final doc = _documentsByName[package]!; - return doc.grantedPoints < query.minPoints!; - }); + packageScores.removeWhere( + (i, _) => _documents[i].grantedPoints < query.minPoints!); } // filter on updatedDuration final updatedDuration = query.parsedQuery.updatedDuration; if (updatedDuration != null && updatedDuration > Duration.zero) { final now = clock.now(); - packages.removeWhere((package) { - final doc = _documentsByName[package]!; - final diff = now.difference(doc.updated); - return diff > updatedDuration; - }); + packageScores.removeWhere( + (i, _) => now.difference(_documents[i].updated) > updatedDuration); } + final packages = packageScores.toKeySet(); // do text matching final parsedQueryText = query.parsedQuery.text; diff --git a/app/lib/search/search_service.dart b/app/lib/search/search_service.dart index 95bda1269d..002b00e29d 100644 --- a/app/lib/search/search_service.dart +++ b/app/lib/search/search_service.dart @@ -132,6 +132,8 @@ class PackageDocument { @JsonKey(includeFromJson: false, includeToJson: false) late final Set tagsForLookup = Set.of(tags); + + late final packageNameLowerCased = package.toLowerCase(); } /// A reference to an API doc page diff --git a/app/lib/search/token_index.dart b/app/lib/search/token_index.dart index fda0f91cf7..95d42a46ba 100644 --- a/app/lib/search/token_index.dart +++ b/app/lib/search/token_index.dart @@ -214,27 +214,26 @@ class TokenIndex { Map _scoreDocs(TokenMatch tokenMatch, {double weight = 1.0, Set? limitToIds}) { // Summarize the scores for the documents. - final docScores = List.filled(_length, 0.0); + final scores = IndexedScore(_ids); for (final token in tokenMatch.tokens) { final docWeights = _inverseIds[token]!; for (final e in docWeights.entries) { - final i = e.key; - docScores[i] = math.max(docScores[i], tokenMatch[token]! * e.value); + scores.setValueMaxOf(e.key, tokenMatch[token]! * e.value); } } + if (limitToIds != null) { + scores.retainWhere((_, id) => limitToIds.contains(id)); + } final result = {}; // post-process match weights for (var i = 0; i < _length; i++) { - final id = _ids[i]; - final w = docScores[i]; + final w = scores._values[i]; if (w <= 0.0) { continue; } - if (limitToIds != null && !limitToIds.contains(id)) { - continue; - } - result[id] = w * weight; + final id = _ids[i]; + result[id] = scores._values[i] * weight; } return result; } @@ -269,3 +268,53 @@ class TokenIndex { return Score.multiply(scores); } } + +/// Mutable score list that can accessed via integer index. +class IndexedScore { + final List _keys; + final List _values; + + IndexedScore._(this._keys, this._values); + + factory IndexedScore(List keys, [double value = 0.0]) => + IndexedScore._(keys, List.filled(keys.length, value)); + + late final length = _values.length; + + bool isNotPositive(int index) { + return _values[index] <= 0.0; + } + + void setValueMaxOf(int index, double value) { + _values[index] = math.max(_values[index], value); + } + + void removeWhere(bool Function(int index, String key) fn) { + for (var i = 0; i < length; i++) { + if (isNotPositive(i)) continue; + if (fn(i, _keys[i])) { + _values[i] = 0.0; + } + } + } + + void retainWhere(bool Function(int index, String key) fn) { + for (var i = 0; i < length; i++) { + if (isNotPositive(i)) continue; + if (!fn(i, _keys[i])) { + _values[i] = 0.0; + } + } + } + + Set toKeySet() { + final set = {}; + for (var i = 0; i < _values.length; i++) { + final v = _values[i]; + if (v > 0.0) { + set.add(_keys[i]); + } + } + return set; + } +}