You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
89 lines
2.4 KiB
89 lines
2.4 KiB
import 'dart:math';
|
|
import '../models/question.dart';
|
|
import 'spaced_repetition.dart';
|
|
|
|
/// Selects questions using weighted random selection.
|
|
class WeightedSelector {
|
|
final Random _random;
|
|
|
|
/// Creates a weighted selector with an optional random seed.
|
|
WeightedSelector({int? seed}) : _random = Random(seed);
|
|
|
|
/// Selects [count] questions from [candidates] using weighted random selection.
|
|
///
|
|
/// [currentAttemptIndex] is used for spaced repetition calculations.
|
|
/// Returns a list of selected questions (no duplicates).
|
|
List<Question> selectQuestions({
|
|
required List<Question> candidates,
|
|
required int count,
|
|
required int currentAttemptIndex,
|
|
}) {
|
|
if (candidates.isEmpty || count <= 0) {
|
|
return [];
|
|
}
|
|
|
|
if (count >= candidates.length) {
|
|
return List.from(candidates);
|
|
}
|
|
|
|
final selected = <Question>[];
|
|
final available = List<Question>.from(candidates);
|
|
|
|
while (selected.length < count && available.isNotEmpty) {
|
|
final question = _selectOne(
|
|
candidates: available,
|
|
currentAttemptIndex: currentAttemptIndex,
|
|
);
|
|
selected.add(question);
|
|
available.remove(question);
|
|
}
|
|
|
|
return selected;
|
|
}
|
|
|
|
/// Selects a single question using weighted random selection.
|
|
Question _selectOne({
|
|
required List<Question> candidates,
|
|
required int currentAttemptIndex,
|
|
}) {
|
|
if (candidates.length == 1) {
|
|
return candidates.first;
|
|
}
|
|
|
|
// Calculate weights for all candidates
|
|
final weights = candidates.map((q) {
|
|
if (q.isKnown) {
|
|
return SpacedRepetition.calculateKnownQuestionWeight(
|
|
lastAttemptIndex: q.lastAttemptIndex,
|
|
currentAttemptIndex: currentAttemptIndex,
|
|
);
|
|
} else {
|
|
// Unknown questions: priority + 1 to ensure non-zero weight
|
|
return (q.priorityPoints + 1).toDouble();
|
|
}
|
|
}).toList();
|
|
|
|
// Calculate cumulative weights
|
|
final cumulativeWeights = <double>[];
|
|
double sum = 0.0;
|
|
for (final weight in weights) {
|
|
sum += weight;
|
|
cumulativeWeights.add(sum);
|
|
}
|
|
|
|
// Select random value in range [0, sum)
|
|
final randomValue = _random.nextDouble() * sum;
|
|
|
|
// Find the index corresponding to the random value
|
|
for (int i = 0; i < cumulativeWeights.length; i++) {
|
|
if (randomValue < cumulativeWeights[i]) {
|
|
return candidates[i];
|
|
}
|
|
}
|
|
|
|
// Fallback to last item (shouldn't happen, but safety)
|
|
return candidates.last;
|
|
}
|
|
}
|
|
|