Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions app/lib/search/mem_index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -303,15 +303,15 @@ class InMemoryPackageIndex {

final core = Score.multiply(coreScores);

var symbolPages = Score.empty();
var symbolPages = Score.empty;
if (!checkAborted()) {
symbolPages = _apiSymbolIndex.searchWords(words, weight: 0.70);
}

final apiPackages = <String, double>{};
final topApiPages = <String, List<MapEntry<String, double>>>{};
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;

Expand Down Expand Up @@ -344,14 +344,15 @@ class InMemoryPackageIndex {
final phrases = extractExactPhrases(text);
if (!aborted && phrases.isNotEmpty) {
final matched = <String, double>{};
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);
Expand Down Expand Up @@ -444,7 +445,7 @@ class _TextResults {
final List<String>? nameMatches;

factory _TextResults.empty() => _TextResults(
Score.empty(),
Score.empty,
{},
nameMatches: null,
);
Expand Down
6 changes: 2 additions & 4 deletions app/lib/search/sdk_mem_index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
61 changes: 24 additions & 37 deletions app/lib/search/token_index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, double> _values;
extension type const Score._(Map<String, double> _values)
implements Map<String, double> {
static const Score empty = Score._({});

Score(this._values);
Score.empty() : _values = const <String, double>{};

bool get isEmpty => _values.isEmpty;
bool get isNotEmpty => !isEmpty;
factory Score.fromEntries(Iterable<MapEntry<String, double>> entries) =>
Score(Map.fromEntries(entries));

Set<String> 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<String, double> 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<Score> 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 = <String, double>{};
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;
}
Expand All @@ -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 = <String, double>{};
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);
}
Expand All @@ -97,24 +90,18 @@ class Score {
if (threshold == null) {
return this;
}
final result = Map<String, double>.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<String> keys) {
final result = Map<String, double>.fromEntries(
_values.entries.where((entry) => keys.contains(entry.key)));
return Score(result);
}
Score project(Set<String> 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<String, double>.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}) {
Expand Down Expand Up @@ -276,7 +263,7 @@ class TokenIndex {
Score searchWords(List<String> words,
{double weight = 1.0, Set<String>? limitToIds}) {
if (limitToIds != null && limitToIds.isEmpty) {
return Score.empty();
return Score.empty;
}
final scores = <Score>[];
for (final w in words) {
Expand All @@ -288,7 +275,7 @@ class TokenIndex {
limitToIds: limitToIds,
);
if (values.isEmpty) {
return Score.empty();
return Score.empty;
}
scores.add(Score(values));
}
Expand Down
24 changes: 12 additions & 12 deletions app/test/search/package_name_index_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -80,7 +80,7 @@ void main() {

test('prefix: fl', () {
expect(
index.search('fl').getValues(),
index.search('fl'),
{
'fluent': 1.0,
'fluent_ui': 1.0,
Expand All @@ -91,7 +91,7 @@ void main() {

test('prefix: flu', () {
expect(
index.search('flu').getValues(),
index.search('flu'),
{
'fluent': 1.0,
'fluent_ui': 1.0,
Expand All @@ -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'),
{},
);
});
Expand Down
12 changes: 6 additions & 6 deletions app/test/search/token_index_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});
});
});
}
Loading