namespace QuestionsApi.Models; public static class ChoicePoolSelector { private const int PresentedChoiceCount = 5; private const double MaximumRateMultiplier = 5.0; public static List SelectChoiceIds( Question question, IReadOnlyDictionary? liveSelectionPercentages = null, Random? random = null) { random ??= Random.Shared; var rates = liveSelectionPercentages is { Count: > 0 } ? liveSelectionPercentages : question.ChoiceSelectionPercentages; var correct = question.Choices.FirstOrDefault(choice => choice.Id == question.CorrectChoiceId); var remaining = question.Choices .Where(choice => choice.Id != question.CorrectChoiceId) .ToList(); var selected = new List(); while (selected.Count < PresentedChoiceCount - 1 && remaining.Count > 0) { var totalWeight = remaining.Sum(choice => Weight(choice.Id, rates)); var threshold = random.NextDouble() * totalWeight; var cumulative = 0.0; var selectedIndex = remaining.Count - 1; for (var index = 0; index < remaining.Count; index++) { cumulative += Weight(remaining[index].Id, rates); if (threshold < cumulative) { selectedIndex = index; break; } } selected.Add(remaining[selectedIndex]); remaining.RemoveAt(selectedIndex); } if (correct is not null) selected.Add(correct); else selected = question.Choices.Take(PresentedChoiceCount).ToList(); Shuffle(selected, random); return selected.Select(choice => choice.Id).ToList(); } private static double Weight(string choiceId, IReadOnlyDictionary? rates) { var rate = Math.Clamp(rates?.GetValueOrDefault(choiceId, 0) ?? 0, 0, 100); return 1.0 + MaximumRateMultiplier * rate / 100.0; } private static void Shuffle(IList items, Random random) { for (var index = items.Count - 1; index > 0; index--) { var replacement = random.Next(index + 1); (items[index], items[replacement]) = (items[replacement], items[index]); } } }