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 selectQuestions({ required List candidates, required int count, required int currentAttemptIndex, }) { if (candidates.isEmpty || count <= 0) { return []; } if (count >= candidates.length) { return List.from(candidates); } final selected = []; final available = List.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 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 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; } }