diff --git a/app/lib/search/mem_index.dart b/app/lib/search/mem_index.dart index 3c6cd6b961..495925f796 100644 --- a/app/lib/search/mem_index.dart +++ b/app/lib/search/mem_index.dart @@ -162,7 +162,7 @@ class InMemoryPackageIndex { // filter packages that doesn't match text query if (textResults != null) { - final keys = textResults.pkgScore.getKeys(); + final keys = textResults.pkgScore.keys.toSet(); packages.removeWhere((x) => !keys.contains(x)); } @@ -179,12 +179,12 @@ class InMemoryPackageIndex { /// it linearly into the [0.4-1.0] range, to allow better /// multiplication outcomes. final overallScore = textResults.pkgScore - .map((key, value) => value * _adjustedOverallScores[key]!); - packageHits = _rankWithValues(overallScore.getValues()); + .mapValues((key, value) => value * _adjustedOverallScores[key]!); + packageHits = _rankWithValues(overallScore); break; case SearchOrder.text: - final score = textResults?.pkgScore ?? Score.empty(); - packageHits = _rankWithValues(score.getValues()); + final score = textResults?.pkgScore ?? Score.empty; + packageHits = _rankWithValues(score); break; case SearchOrder.created: packageHits = _createdOrderedHits.whereInSet(packages); @@ -294,7 +294,7 @@ class InMemoryPackageIndex { coreScores.add(score); // don't update if the query is single-word if (words.length > 1) { - wordScopedPackages = score.getKeys(); + wordScopedPackages = score.keys.toSet(); if (wordScopedPackages.isEmpty) { break; } @@ -303,7 +303,7 @@ class InMemoryPackageIndex { final core = Score.multiply(coreScores); - var symbolPages = Score.empty(); + var symbolPages = Score.empty; if (!checkAborted()) { symbolPages = _apiSymbolIndex.searchWords(words, weight: 0.70); } @@ -311,7 +311,7 @@ class InMemoryPackageIndex { final apiPackages = {}; final topApiPages = >>{}; const maxApiPageCount = 2; - for (final entry in symbolPages.getValues().entries) { + for (final entry in symbolPages.entries) { final pkg = _apiDocPkg(entry.key); if (!packages.contains(pkg)) continue; @@ -344,14 +344,15 @@ class InMemoryPackageIndex { final phrases = extractExactPhrases(text); if (!aborted && phrases.isNotEmpty) { final matched = {}; - for (final package in score.getKeys()) { + for (final MapEntry(key: package, value: packageScore) + in score.entries) { final doc = _documentsByName[package]!; final bool matchedAllPhrases = phrases.every((phrase) => doc.package.contains(phrase) || doc.description!.contains(phrase) || doc.readme!.contains(phrase)); if (matchedAllPhrases) { - matched[package] = score[package]; + matched[package] = packageScore; } } score = Score(matched); @@ -444,7 +445,7 @@ class _TextResults { final List? nameMatches; factory _TextResults.empty() => _TextResults( - Score.empty(), + Score.empty, {}, nameMatches: null, ); diff --git a/app/lib/search/sdk_mem_index.dart b/app/lib/search/sdk_mem_index.dart index de7248af01..0e9b8e49c0 100644 --- a/app/lib/search/sdk_mem_index.dart +++ b/app/lib/search/sdk_mem_index.dart @@ -141,7 +141,7 @@ class SdkMemIndex { final libraryWeight = _libraryWeights[library] ?? 1.0; final weightedResults = isQualifiedQuery ? plainResults - : plainResults.map( + : plainResults.mapValues( (key, value) { final dir = p.dirname(key); final w = (_apiPageDirWeights[dir] ?? 1.0) * libraryWeight; @@ -169,9 +169,7 @@ class SdkMemIndex { description: _descriptionPerLibrary[hit.library], url: _baseUriPerLibrary[hit.library] ?? _baseUri.toString(), score: hit.score, - apiPages: hit.top - .getValues() - .entries + apiPages: hit.top.entries .map( (e) => ApiPageRef( path: e.key, diff --git a/app/lib/search/token_index.dart b/app/lib/search/token_index.dart index df6a6e607e..40a2410fd8 100644 --- a/app/lib/search/token_index.dart +++ b/app/lib/search/token_index.dart @@ -9,47 +9,40 @@ import 'package:meta/meta.dart'; import 'text_utils.dart'; /// Represents an evaluated score as an {id: score} map. -class Score { - final Map _values; +extension type const Score._(Map _values) + implements Map { + static const Score empty = Score._({}); Score(this._values); - Score.empty() : _values = const {}; - bool get isEmpty => _values.isEmpty; - bool get isNotEmpty => !isEmpty; + factory Score.fromEntries(Iterable> entries) => + Score(Map.fromEntries(entries)); - Set getKeys({bool Function(String key)? where}) => - _values.keys.where((e) => where == null || where(e)).toSet(); - late final double maxValue = _values.values.fold(0.0, math.max); - Map getValues() => _values; - bool containsKey(String key) => _values.containsKey(key); - int get length => _values.length; - - double operator [](String key) => _values[key] ?? 0.0; + double get maxValue => _values.values.fold(0.0, math.max); /// Calculates the intersection of the [scores], by multiplying the values. static Score multiply(List scores) { if (scores.isEmpty) { - return Score.empty(); + return Score.empty; } if (scores.length == 1) { return scores.single; } if (scores.any((score) => score.isEmpty)) { - return Score.empty(); + return Score.empty; } - var keys = scores.first.getValues().keys.toSet(); + var keys = scores.first.keys.toSet(); for (var i = 1; i < scores.length; i++) { - keys = keys.intersection(scores[i].getValues().keys.toSet()); + keys = keys.intersection(scores[i].keys.toSet()); } if (keys.isEmpty) { - return Score.empty(); + return Score.empty; } final values = {}; for (final key in keys) { - var value = scores.first.getValues()[key]!; + var value = scores.first[key]!; for (var i = 1; i < scores.length; i++) { - value *= scores[i].getValues()[key]!; + value *= scores[i][key]!; } values[key] = value; } @@ -63,17 +56,17 @@ class Score { scores.removeWhere((s) => s.isEmpty); if (scores.isEmpty) { - return Score.empty(); + return Score.empty; } if (scores.length == 1) { return scores.single; } - final keys = scores.expand((e) => e.getValues().keys).toSet(); + final keys = scores.expand((e) => e.keys).toSet(); final result = {}; for (final key in keys) { var value = 0.0; for (var i = 0; i < scores.length; i++) { - final v = scores[i].getValues()[key]; + final v = scores[i][key]; if (v != null) { value = math.max(value, v); } @@ -97,24 +90,18 @@ class Score { if (threshold == null) { return this; } - final result = Map.fromEntries( + return Score.fromEntries( _values.entries.where((entry) => entry.value >= threshold!)); - return Score(result); } /// Keeps the scores only for values in [keys]. - Score project(Set keys) { - final result = Map.fromEntries( - _values.entries.where((entry) => keys.contains(entry.key))); - return Score(result); - } + Score project(Set keys) => Score.fromEntries( + _values.entries.where((entry) => keys.contains(entry.key))); /// Transfer the score values with [f]. - Score map(double Function(String key, double value) f) { - final result = Map.fromEntries( - _values.entries.map((e) => MapEntry(e.key, f(e.key, e.value)))); - return Score(result); - } + Score mapValues(double Function(String key, double value) f) => + Score.fromEntries( + _values.entries.map((e) => MapEntry(e.key, f(e.key, e.value)))); /// Returns a new [Score] object with the top [count] entry. Score top(int count, {double? minValue}) { @@ -276,7 +263,7 @@ class TokenIndex { Score searchWords(List words, {double weight = 1.0, Set? limitToIds}) { if (limitToIds != null && limitToIds.isEmpty) { - return Score.empty(); + return Score.empty; } final scores = []; for (final w in words) { @@ -288,7 +275,7 @@ class TokenIndex { limitToIds: limitToIds, ); if (values.isEmpty) { - return Score.empty(); + return Score.empty; } scores.add(Score(values)); } diff --git a/app/test/search/package_name_index_test.dart b/app/test/search/package_name_index_test.dart index bc820d0c4c..bc9d8bc339 100644 --- a/app/test/search/package_name_index_test.dart +++ b/app/test/search/package_name_index_test.dart @@ -19,7 +19,7 @@ void main() { test('fluent vs fluent_ui', () { expect( - index.search('fluent').getValues(), + index.search('fluent'), { 'fluent': 1.0, 'fluent_ui': 1.0, @@ -29,7 +29,7 @@ void main() { test('get vs get_it', () { expect( - index.search('get').getValues(), + index.search('get'), { 'get': 1.0, 'get_it': 1.0, @@ -38,14 +38,14 @@ void main() { }); test('get_it', () { - expect(index.search('get_it').getValues(), { + expect(index.search('get_it'), { 'get_it': 1.0, }); }); test('modular vs modular_flutter', () { expect( - index.search('modular').getValues(), + index.search('modular'), { 'modular': 1.0, 'modular_flutter': 1.0, @@ -55,21 +55,21 @@ void main() { test('mixed parts: fluent it', () { expect( - index.search('fluent it').getValues(), + index.search('fluent it'), {}, ); }); test('mixed parts: fluent flutter', () { expect( - index.search('fluent flutter').getValues(), + index.search('fluent flutter'), {}, ); }); test('prefix: f', () { expect( - index.search('f').getValues(), + index.search('f'), { 'fluent': 1.0, 'fluent_ui': 1.0, @@ -80,7 +80,7 @@ void main() { test('prefix: fl', () { expect( - index.search('fl').getValues(), + index.search('fl'), { 'fluent': 1.0, 'fluent_ui': 1.0, @@ -91,7 +91,7 @@ void main() { test('prefix: flu', () { expect( - index.search('flu').getValues(), + index.search('flu'), { 'fluent': 1.0, 'fluent_ui': 1.0, @@ -102,21 +102,21 @@ void main() { test('prefix: fluf', () { expect( - index.search('fluf').getValues(), + index.search('fluf'), {'fluent': 0.5, 'fluent_ui': 0.5, 'modular_flutter': 0.5}, ); }); test('prefix: fluff', () { expect( - index.search('fluff').getValues(), + index.search('fluff'), {}, ); }); test('prefix: fluffy', () { expect( - index.search('fluffy').getValues(), + index.search('fluffy'), {}, ); }); diff --git a/app/test/search/token_index_test.dart b/app/test/search/token_index_test.dart index 47f2393597..3bcf2bb6e4 100644 --- a/app/test/search/token_index_test.dart +++ b/app/test/search/token_index_test.dart @@ -113,24 +113,24 @@ void main() { }); test('remove low scores', () { - expect(score.getValues(), { + expect(score, { 'a': 100.0, 'b': 30.0, 'c': 55.0, }); - expect(score.removeLowValues(fraction: 0.31).getValues(), { + expect(score.removeLowValues(fraction: 0.31), { 'a': 100.0, 'c': 55.0, }); - expect(score.removeLowValues(minValue: 56.0).getValues(), { + expect(score.removeLowValues(minValue: 56.0), { 'a': 100.0, }); }); test('top', () { - expect(score.top(1).getValues(), {'a': 100.0}); - expect(score.top(2).getValues(), {'a': 100.0, 'c': 55.0}); - expect(score.top(2, minValue: 60).getValues(), {'a': 100.0}); + expect(score.top(1), {'a': 100.0}); + expect(score.top(2), {'a': 100.0, 'c': 55.0}); + expect(score.top(2, minValue: 60), {'a': 100.0}); }); }); }