Prefer high-performing distractors
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class QuestionResponse
|
||||
int questionElo = 1000,
|
||||
bool includeChoicePool = true)
|
||||
{
|
||||
var choices = includeChoicePool ? question.Choices : SelectLegacyChoices(question);
|
||||
var choices = includeChoicePool ? question.Choices : SelectLegacyChoices(question, liveChoiceSelectionPercentages);
|
||||
var choiceIds = choices.Select(choice => choice.Id).ToHashSet(StringComparer.Ordinal);
|
||||
var percentages = BuildChoiceSelectionPercentages(question, liveChoiceSelectionPercentages);
|
||||
return new QuestionResponse
|
||||
@@ -87,20 +87,17 @@ public class QuestionResponse
|
||||
return response;
|
||||
}
|
||||
|
||||
private static List<QuestionChoice> SelectLegacyChoices(Question question)
|
||||
private static List<QuestionChoice> SelectLegacyChoices(
|
||||
Question question,
|
||||
IReadOnlyDictionary<string, int>? liveChoiceSelectionPercentages)
|
||||
{
|
||||
if (question.Choices.Count <= 5)
|
||||
return question.Choices;
|
||||
|
||||
var correct = question.Choices.First(choice => choice.Id == question.CorrectChoiceId);
|
||||
var distractors = question.Choices
|
||||
.Where(choice => choice.Id != question.CorrectChoiceId)
|
||||
.OrderBy(_ => Random.Shared.Next())
|
||||
.Take(4)
|
||||
.Append(correct)
|
||||
.OrderBy(_ => Random.Shared.Next())
|
||||
var selectedIds = ChoicePoolSelector.SelectChoiceIds(question, liveChoiceSelectionPercentages);
|
||||
return selectedIds
|
||||
.Select(id => question.Choices.First(choice => choice.Id == id))
|
||||
.ToList();
|
||||
return distractors;
|
||||
}
|
||||
|
||||
public static Dictionary<string, int> BuildChoiceSelectionPercentages(Question question, Dictionary<string, int>? liveChoiceSelectionPercentages)
|
||||
|
||||
Reference in New Issue
Block a user