From 363c632e6abdb6a8be5a99ee8dbc56868fa26de8 Mon Sep 17 00:00:00 2001 From: Edlund Date: Sun, 3 Mar 2024 11:38:25 +0100 Subject: [PATCH 1/2] feat: sort by longest exact match --- .../immersion_kit_enhancement.dart | 66 +++++++++++++++---- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart b/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart index 604798ae..6a1a6368 100644 --- a/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart +++ b/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -26,6 +27,7 @@ class ImmersionKitResult { required this.wordList, required this.wordIndices, required this.calculateRange, + required this.longestExactMatch, }); /// The sentence in plain unformatted form. @@ -48,6 +50,9 @@ class ImmersionKitResult { TextRange? _calculatedRange; + /// How many consecutive characters match the search term exactly + int longestExactMatch; + /// Function to calculate the range of search term TextRange Function() calculateRange; @@ -236,6 +241,31 @@ class ImmersionKitEnhancement extends Enhancement { } } + int _longestExactRangeForResult({ + required List wordIndices, + required List wordList, + required String term, + required String text, + }) { + /// Start at the first character of the given cloze + int textPosition = wordList.sublist(0, wordIndices.first).join().length; + int termPosition = 0; + + int longest = 0; + + var termChar = term.characters.elementAtOrNull(termPosition); + var textChar = text.characters.elementAtOrNull(textPosition); + while (textChar != null && termChar != null && termChar == textChar) { + longest++; + termPosition++; + textPosition++; + termChar = term.characters.elementAtOrNull(termPosition); + textChar = text.characters.elementAtOrNull(textPosition); + } + + return longest; + } + /// Search the Massif API for example sentences and return a list of results. Future> searchForSentences({ required AppModel appModel, @@ -290,18 +320,23 @@ class ImmersionKitEnhancement extends Enhancement { String audioUrl = example['sound_url']; ImmersionKitResult result = ImmersionKitResult( - text: text, - source: source, - imageUrl: imageUrl, - audioUrl: audioUrl, - wordList: wordList, - wordIndices: wordIndices, - calculateRange: () => _getRangeFromIndexedList( - wordIndices: wordIndices, + text: text, + source: source, + imageUrl: imageUrl, + audioUrl: audioUrl, wordList: wordList, - term: searchTerm, - ), - ); + wordIndices: wordIndices, + calculateRange: () => _getRangeFromIndexedList( + wordIndices: wordIndices, + wordList: wordList, + term: searchTerm, + ), + longestExactMatch: _longestExactRangeForResult( + wordIndices: wordIndices, + wordList: wordList, + text: text, + term: searchTerm, + )); /// Sentence examples that are merely the word itself are pretty /// redundant. @@ -313,7 +348,7 @@ class ImmersionKitEnhancement extends Enhancement { /// Make sure series aren't too consecutive. results.shuffle(); - /// Results with images come first. + /// Sort by: has image -> has audio -> longest exact match -> shortest sentence results.sort((a, b) { int hasImage = (a.imageUrl.isNotEmpty ? -1 : 1) .compareTo(b.imageUrl.isNotEmpty ? -1 : 1); @@ -329,6 +364,13 @@ class ImmersionKitEnhancement extends Enhancement { return hasAudio; } + /// Sort by longest subterm + int longestMatch = b.longestExactMatch.compareTo(a.longestExactMatch); + + if (longestMatch != 0) { + return longestMatch; + } + return a.text.length.compareTo(b.text.length); }); From 0ae25053f2e5c9cb573c006853fb4ecbcc2f88f0 Mon Sep 17 00:00:00 2001 From: Edlund Date: Sun, 3 Mar 2024 12:10:39 +0100 Subject: [PATCH 2/2] feat: code cleanup and sorting for massif --- .../immersion_kit_enhancement.dart | 14 ++--- .../massif_example_sentences_enhancement.dart | 53 +++++++++++++++++-- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart b/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart index 6a1a6368..67a1f2e4 100644 --- a/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart +++ b/yuuna/lib/src/creator/enhancements/immersion_kit_enhancement.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -251,19 +250,14 @@ class ImmersionKitEnhancement extends Enhancement { int textPosition = wordList.sublist(0, wordIndices.first).join().length; int termPosition = 0; - int longest = 0; - - var termChar = term.characters.elementAtOrNull(termPosition); - var textChar = text.characters.elementAtOrNull(textPosition); - while (textChar != null && termChar != null && termChar == textChar) { - longest++; + while (textPosition < text.length && + termPosition < term.length && + term[termPosition] == text[textPosition]) { termPosition++; textPosition++; - termChar = term.characters.elementAtOrNull(termPosition); - textChar = text.characters.elementAtOrNull(textPosition); } - return longest; + return termPosition; } /// Search the Massif API for example sentences and return a list of results. diff --git a/yuuna/lib/src/creator/enhancements/massif_example_sentences_enhancement.dart b/yuuna/lib/src/creator/enhancements/massif_example_sentences_enhancement.dart index d076847d..769a9330 100644 --- a/yuuna/lib/src/creator/enhancements/massif_example_sentences_enhancement.dart +++ b/yuuna/lib/src/creator/enhancements/massif_example_sentences_enhancement.dart @@ -16,6 +16,7 @@ class MassifResult { required this.range, required this.source, required this.spans, + required this.longestExactMatch, }); /// The sentence in plain unformatted form. @@ -27,6 +28,9 @@ class MassifResult { /// A formatted widget which may contain highlighted text. List spans; + /// How many consecutive characters match the search term exactly + int longestExactMatch; + /// First selected range. TextRange range; @@ -102,6 +106,29 @@ class MassifExampleSentencesEnhancement extends Enhancement { ); } + int _longestExactRangeForResult({ + required int? start, + required String term, + required String text, + }) { + if (start == null) { + return 0; + } + + /// Start at the first character of the given cloze + int textPosition = start; + int termPosition = 0; + + while (textPosition < text.length && + termPosition < term.length && + term[termPosition] == text[textPosition]) { + termPosition++; + textPosition++; + } + + return termPosition; + } + /// Search the Massif API for example sentences and return a list of results. Future> searchForSentences({ required BuildContext context, @@ -209,11 +236,27 @@ class MassifExampleSentencesEnhancement extends Enhancement { } MassifResult result = MassifResult( - text: text, - range: range, - source: source, - spans: spans, - ); + text: text, + range: range, + source: source, + spans: spans, + longestExactMatch: _longestExactRangeForResult( + start: start, + term: searchTerm, + text: text, + )); + + /// Sort by: longest exact match -> shortest sentence + results.sort((a, b) { + /// Sort by longest subterm + int longestMatch = b.longestExactMatch.compareTo(a.longestExactMatch); + + if (longestMatch != 0) { + return longestMatch; + } + + return a.text.length.compareTo(b.text.length); + }); results.add(result); }