Files
step3/api/QuestionsApi/Models/ChoicePoolSelector.cs
2026-07-20 02:44:58 -05:00

66 lines
2.3 KiB
C#

namespace QuestionsApi.Models;
public static class ChoicePoolSelector
{
private const int PresentedChoiceCount = 5;
private const double MaximumRateMultiplier = 5.0;
public static List<string> SelectChoiceIds(
Question question,
IReadOnlyDictionary<string, int>? 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<QuestionChoice>();
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<string, int>? rates)
{
var rate = Math.Clamp(rates?.GetValueOrDefault(choiceId, 0) ?? 0, 0, 100);
return 1.0 + MaximumRateMultiplier * rate / 100.0;
}
private static void Shuffle<T>(IList<T> 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]);
}
}
}