57 lines
1.6 KiB
Dart
57 lines
1.6 KiB
Dart
import 'dart:math';
|
|
|
|
import 'spelling_item_record.dart';
|
|
import 'spelling_items_db.dart';
|
|
import 'spelling_progress_policy.dart';
|
|
|
|
/// Picks a shared spelling item for a user using familiarity-weighted randomness.
|
|
class SpellingItemSelector {
|
|
SpellingItemSelector({
|
|
SpellingItemsDb? itemsDb,
|
|
Random? random,
|
|
}) : _itemsDb = itemsDb,
|
|
_random = random ?? Random();
|
|
|
|
final SpellingItemsDb? _itemsDb;
|
|
final Random _random;
|
|
|
|
Future<SpellingItemRecord?> pickForUser(String firebaseUid) async {
|
|
final SpellingItemsDb? itemsDb = _itemsDb;
|
|
if (itemsDb == null) {
|
|
throw StateError('SpellingItemSelector requires itemsDb for pickForUser');
|
|
}
|
|
final List<SpellingItemRecord> items =
|
|
await itemsDb.listActiveWithProgress(firebaseUid);
|
|
if (items.isEmpty) {
|
|
return null;
|
|
}
|
|
return _weightedPick(items);
|
|
}
|
|
|
|
/// Visible for unit tests.
|
|
SpellingItemRecord? pickFromCandidates(List<SpellingItemRecord> items) {
|
|
if (items.isEmpty) {
|
|
return null;
|
|
}
|
|
return _weightedPick(items);
|
|
}
|
|
|
|
SpellingItemRecord _weightedPick(List<SpellingItemRecord> items) {
|
|
final List<double> weights = items
|
|
.map(
|
|
(SpellingItemRecord item) =>
|
|
SpellingProgressPolicy.weightForScore(item.familiarityScore),
|
|
)
|
|
.toList(growable: false);
|
|
final double total = weights.fold<double>(0, (double a, double b) => a + b);
|
|
var roll = _random.nextDouble() * total;
|
|
for (var i = 0; i < items.length; i++) {
|
|
roll -= weights[i];
|
|
if (roll < 0) {
|
|
return items[i];
|
|
}
|
|
}
|
|
return items.last;
|
|
}
|
|
}
|