1 Commits

Author SHA1 Message Date
52e9954c71 WIP: search/word_search: do a bit of performance optimization on result regrouping
All checks were successful
Build and test / evals (push) Successful in 11m34s
2026-03-04 18:02:21 +09:00

View File

@@ -21,49 +21,83 @@ List<WordSearchResult> regroupWordSearchResults({
}) { }) {
final List<WordSearchResult> results = []; final List<WordSearchResult> results = [];
final commonEntryIds = linearWordQueryData.commonEntries final Set<int> commonEntryIds = linearWordQueryData.commonEntries
.map((entry) => entry['entryId'] as int) .map((entry) => entry['entryId'] as int)
.toSet(); .toSet();
final Map<int, List<Map<String, Object?>>> entryReadingElementsByEntryId =
linearWordQueryData.readingElements.groupListsBy(
(element) => element['entryId'] as int,
);
final Map<int, List<Map<String, Object?>>> entryKanjiElementsByEntryId =
linearWordQueryData.kanjiElements.groupListsBy(
(element) => element['entryId'] as int,
);
final Map<int, int> elementIdToEntryId = {
for (final element in linearWordQueryData.readingElements)
element['elementId'] as int: element['entryId'] as int,
for (final element in linearWordQueryData.kanjiElements)
element['elementId'] as int: element['entryId'] as int,
};
final Map<int, List<Map<String, Object?>>> entryReadingElementInfosByEntryId =
linearWordQueryData.readingElementInfos.groupListsBy(
(element) => elementIdToEntryId[element['elementId'] as int]!,
);
final Map<int, List<Map<String, Object?>>> entryKanjiElementInfosByEntryId =
linearWordQueryData.kanjiElementInfos.groupListsBy(
(element) => elementIdToEntryId[element['elementId'] as int]!,
);
final Map<int, List<Map<String, Object?>>>
entryReadingElementRestrictionsByEntryId = linearWordQueryData
.readingElementRestrictions
.groupListsBy(
(element) => elementIdToEntryId[element['elementId'] as int]!,
);
final Map<int, JlptLevel> entryJlptTagsByEntryId = linearWordQueryData
.jlptTags
.groupSetsBy((element) => element['entryId'] as int)
.map(
(final key, final value) => MapEntry(
key,
value.map((e) => JlptLevel.fromString(e['jlptLevel'] as String?)).min,
),
);
final Map<int, List<Map<String, Object?>>> entrySensesByEntryId =
linearWordQueryData.senses.groupListsBy(
(element) => element['entryId'] as int,
);
for (final scoredEntryId in entryIds) { for (final scoredEntryId in entryIds) {
final List<Map<String, Object?>> entryReadingElements = linearWordQueryData final List<Map<String, Object?>> entryReadingElements =
.readingElements entryReadingElementsByEntryId[scoredEntryId.entryId] ?? const [];
.where((element) => element['entryId'] == scoredEntryId.entryId) final List<Map<String, Object?>> entryKanjiElements =
.toList(); entryKanjiElementsByEntryId[scoredEntryId.entryId] ?? const [];
final List<Map<String, Object?>> entryReadingElementInfos =
final List<Map<String, Object?>> entryKanjiElements = linearWordQueryData entryReadingElementInfosByEntryId[scoredEntryId.entryId] ?? const [];
.kanjiElements final List<Map<String, Object?>> entryKanjiElementInfos =
.where((element) => element['entryId'] == scoredEntryId.entryId) entryKanjiElementInfosByEntryId[scoredEntryId.entryId] ?? const [];
.toList(); final List<Map<String, Object?>> entryReadingElementRestrictions =
entryReadingElementRestrictionsByEntryId[scoredEntryId.entryId] ??
final List<Map<String, Object?>> entryJlptTags = linearWordQueryData const [];
.jlptTags
.where((element) => element['entryId'] == scoredEntryId.entryId)
.toList();
final jlptLevel =
entryJlptTags
.map((e) => JlptLevel.fromString(e['jlptLevel'] as String?))
.sorted((a, b) => b.compareTo(a))
.firstOrNull ??
JlptLevel.none;
final isCommon = commonEntryIds.contains(scoredEntryId.entryId);
final List<Map<String, Object?>> entrySenses = linearWordQueryData.senses
.where((element) => element['entryId'] == scoredEntryId.entryId)
.toList();
final GroupedWordResult entryReadingElementsGrouped = _regroupWords( final GroupedWordResult entryReadingElementsGrouped = _regroupWords(
entryId: scoredEntryId.entryId,
readingElements: entryReadingElements,
kanjiElements: entryKanjiElements, kanjiElements: entryKanjiElements,
readingElementInfos: linearWordQueryData.readingElementInfos, kanjiElementInfos: entryKanjiElementInfos,
readingElementRestrictions: readingElements: entryReadingElements,
linearWordQueryData.readingElementRestrictions, readingElementInfos: entryReadingElementInfos,
kanjiElementInfos: linearWordQueryData.kanjiElementInfos, readingElementRestrictions: entryReadingElementRestrictions,
); );
final List<Map<String, Object?>> entrySenses =
entrySensesByEntryId[scoredEntryId.entryId] ?? const [];
final List<WordSearchSense> entrySensesGrouped = _regroupSenses( final List<WordSearchSense> entrySensesGrouped = _regroupSenses(
senses: entrySenses, senses: entrySenses,
senseAntonyms: linearWordQueryData.senseAntonyms, senseAntonyms: linearWordQueryData.senseAntonyms,
@@ -82,6 +116,10 @@ List<WordSearchResult> regroupWordSearchResults({
senseAntonymsXrefData: linearWordQueryData.senseAntonymData, senseAntonymsXrefData: linearWordQueryData.senseAntonymData,
); );
final bool isCommon = commonEntryIds.contains(scoredEntryId.entryId);
final JlptLevel jlptLevel =
entryJlptTagsByEntryId[scoredEntryId.entryId] ?? JlptLevel.none;
results.add( results.add(
WordSearchResult( WordSearchResult(
score: scoredEntryId.score, score: scoredEntryId.score,
@@ -113,7 +151,6 @@ class GroupedWordResult {
} }
GroupedWordResult _regroupWords({ GroupedWordResult _regroupWords({
required int entryId,
required List<Map<String, Object?>> kanjiElements, required List<Map<String, Object?>> kanjiElements,
required List<Map<String, Object?>> kanjiElementInfos, required List<Map<String, Object?>> kanjiElementInfos,
required List<Map<String, Object?>> readingElements, required List<Map<String, Object?>> readingElements,
@@ -122,36 +159,34 @@ GroupedWordResult _regroupWords({
}) { }) {
final List<WordSearchRuby> rubys = []; final List<WordSearchRuby> rubys = [];
final kanjiElements_ = kanjiElements final Map<int, Set<String>> readingElementRestrictionsSet =
.where((element) => element['entryId'] == entryId) readingElementRestrictions
.toList(); .groupSetsBy((element) => element['elementId'] as int)
.map(
(key, value) => MapEntry(
key,
value.map((e) => e['restriction'] as String).toSet(),
),
);
final readingElements_ = readingElements // Construct a cartesian product of kanji + readings, with exceptions made for items marked in `restrictions`.
.where((element) => element['entryId'] == entryId) for (final readingElement in readingElements) {
.toList(); if (readingElement['doesNotMatchKanji'] == 1 || kanjiElements.isEmpty) {
final readingElementRestrictions_ = readingElementRestrictions
.where((element) => element['entryId'] == entryId)
.toList();
for (final readingElement in readingElements_) {
if (readingElement['doesNotMatchKanji'] == 1 || kanjiElements_.isEmpty) {
final ruby = WordSearchRuby(base: readingElement['reading'] as String); final ruby = WordSearchRuby(base: readingElement['reading'] as String);
rubys.add(ruby); rubys.add(ruby);
continue; continue;
} }
for (final kanjiElement in kanjiElements_) { for (final kanjiElement in kanjiElements) {
final kanji = kanjiElement['reading'] as String; final kanji = kanjiElement['reading'] as String;
final reading = readingElement['reading'] as String; final reading = readingElement['reading'] as String;
final restrictions = readingElementRestrictions_ // The 'restrictions' act as an allowlist, meaning that non-matching kanji elements should be ignored.
.where((element) => element['reading'] == reading) final restrictions =
.toList(); readingElementRestrictionsSet[readingElement['elementId'] as int] ??
{};
if (restrictions.isNotEmpty && if (restrictions.isNotEmpty && !restrictions.contains(kanji)) {
!restrictions.any((element) => element['restriction'] == kanji)) {
continue; continue;
} }
@@ -160,35 +195,30 @@ GroupedWordResult _regroupWords({
} }
} }
assert(rubys.isNotEmpty, 'No readings found for entryId: $entryId'); assert(
rubys.isNotEmpty,
'No readings found for entryId: ${kanjiElements.firstOrNull?['entryId'] ?? readingElements.firstOrNull?['entryId'] ?? '???'}',
);
final Map<int, String> readingElementIdsToReading = { final Map<int, String> readingElementIdsToReading = {
for (final element in readingElements_) for (final element in readingElements)
element['elementId'] as int: element['reading'] as String, element['elementId'] as int: element['reading'] as String,
}; };
final Map<int, String> kanjiElementIdsToReading = { final Map<int, String> kanjiElementIdsToReading = {
for (final element in kanjiElements_) for (final element in kanjiElements)
element['elementId'] as int: element['reading'] as String, element['elementId'] as int: element['reading'] as String,
}; };
final readingElementInfos_ = readingElementInfos
.where((element) => element['entryId'] == entryId)
.toList();
final kanjiElementInfos_ = kanjiElementInfos
.where((element) => element['entryId'] == entryId)
.toList();
return GroupedWordResult( return GroupedWordResult(
rubys: rubys, rubys: rubys,
readingInfos: { readingInfos: {
for (final rei in readingElementInfos_) for (final rei in readingElementInfos)
readingElementIdsToReading[rei['elementId'] as int]!: readingElementIdsToReading[rei['elementId'] as int]!:
JMdictReadingInfo.fromId(rei['info'] as String), JMdictReadingInfo.fromId(rei['info'] as String),
}, },
kanjiInfos: { kanjiInfos: {
for (final kei in kanjiElementInfos_) for (final kei in kanjiElementInfos)
kanjiElementIdsToReading[kei['elementId'] as int]!: kanjiElementIdsToReading[kei['elementId'] as int]!:
JMdictKanjiInfo.fromId(kei['info'] as String), JMdictKanjiInfo.fromId(kei['info'] as String),
}, },