fluffychat/lib/pangea/practice_activities/lemma_activity_generator.dart
2025-12-23 14:35:41 -05:00

110 lines
3.3 KiB
Dart

import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
class LemmaActivityGenerator {
static Future<MessageActivityResponse> get(
MessageActivityRequest req,
) async {
debugger(when: kDebugMode && req.targetTokens.length != 1);
final token = req.targetTokens.first;
final choices = await _lemmaActivityDistractors(token);
// TODO - modify MultipleChoiceActivity flow to allow no correct answer
return MessageActivityResponse(
activity: PracticeActivityModel(
activityType: ActivityTypeEnum.lemmaId,
targetTokens: [token],
langCode: req.userL2,
multipleChoiceContent: MultipleChoiceActivity(
choices: choices,
answers: {token.lemma.text},
),
),
);
}
static Future<Set<String>> _lemmaActivityDistractors(
PangeaToken token,
) async {
final constructs = await MatrixState
.pangeaController.matrixState.analyticsDataService
.getAggregatedConstructs(ConstructTypeEnum.vocab);
final List<String> lemmas =
constructs.values.map((c) => c.lemma).toSet().toList();
// Offload computation to an isolate
final Map<String, int> distances =
await compute(_computeDistancesInIsolate, {
'lemmas': lemmas,
'target': token.lemma.text,
});
// Sort lemmas by distance
final sortedLemmas = distances.keys.toList()
..sort((a, b) => distances[a]!.compareTo(distances[b]!));
// Take the shortest 4
final choices = sortedLemmas.take(4).toSet();
if (choices.isEmpty) {
return {token.lemma.text};
}
if (!choices.contains(token.lemma.text)) {
choices.add(token.lemma.text);
}
return choices;
}
// isolate helper function
static Map<String, int> _computeDistancesInIsolate(
Map<String, dynamic> params,
) {
final List<String> lemmas = params['lemmas'];
final String target = params['target'];
// Calculate Levenshtein distances
final Map<String, int> distances = {};
for (final lemma in lemmas) {
distances[lemma] = _levenshteinDistanceSync(target, lemma);
}
return distances;
}
static int _levenshteinDistanceSync(String s, String t) {
final int m = s.length;
final int n = t.length;
final List<List<int>> dp = List.generate(
m + 1,
(_) => List.generate(n + 1, (_) => 0),
);
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (i == 0) {
dp[i][j] = j;
} else if (j == 0) {
dp[i][j] = i;
} else if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 +
[dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]]
.reduce((a, b) => a < b ? a : b);
}
}
}
return dp[m][n];
}
}