dramaling-vocab-learning/backend/DramaLing.Api/Services/SM2Algorithm.cs

162 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace DramaLing.Api.Services;
public record SM2Input(
int Quality, // 1-5 評分
float EasinessFactor, // 難度係數
int Repetitions, // 重複次數
int IntervalDays // 當前間隔天數
);
public record SM2Result(
float EasinessFactor, // 新的難度係數
int Repetitions, // 新的重複次數
int IntervalDays, // 新的間隔天數
DateTime NextReviewDate // 下次複習日期
);
public static class SM2Algorithm
{
// SM-2 算法常數
private const float MIN_EASINESS_FACTOR = 1.3f;
private const float MAX_EASINESS_FACTOR = 2.5f;
private const float INITIAL_EASINESS_FACTOR = 2.5f;
private const int MIN_INTERVAL = 1;
private const int MAX_INTERVAL = 365;
/// <summary>
/// 計算下次複習的間隔和參數
/// </summary>
public static SM2Result Calculate(SM2Input input)
{
var (quality, easinessFactor, repetitions, intervalDays) = input;
// 驗證輸入參數
if (quality < 1 || quality > 5)
throw new ArgumentException("Quality must be between 1 and 5", nameof(input));
// 更新難度係數
var newEasinessFactor = UpdateEasinessFactor(easinessFactor, quality);
int newRepetitions;
int newIntervalDays;
// 如果回答錯誤 (quality < 3),重置進度
if (quality < 3)
{
newRepetitions = 0;
newIntervalDays = 1;
}
else
{
// 如果回答正確,增加重複次數並計算新間隔
newRepetitions = repetitions + 1;
newIntervalDays = newRepetitions switch
{
1 => 1,
2 => 6,
_ => (int)Math.Round(intervalDays * newEasinessFactor)
};
}
// 限制間隔範圍
newIntervalDays = Math.Clamp(newIntervalDays, MIN_INTERVAL, MAX_INTERVAL);
// 計算下次複習日期
var nextReviewDate = DateTime.Today.AddDays(newIntervalDays);
return new SM2Result(
newEasinessFactor,
newRepetitions,
newIntervalDays,
nextReviewDate
);
}
/// <summary>
/// 更新難度係數
/// </summary>
private static float UpdateEasinessFactor(float currentEF, int quality)
{
// SM-2 公式EF' = EF + (0.1 - (5-q) * (0.08 + (5-q) * 0.02))
var newEF = currentEF + (0.1f - (5 - quality) * (0.08f + (5 - quality) * 0.02f));
// 限制在有效範圍內
return Math.Clamp(newEF, MIN_EASINESS_FACTOR, MAX_EASINESS_FACTOR);
}
/// <summary>
/// 獲取初始參數(新詞卡)
/// </summary>
public static SM2Input GetInitialParameters()
{
return new SM2Input(
Quality: 3,
EasinessFactor: INITIAL_EASINESS_FACTOR,
Repetitions: 0,
IntervalDays: 1
);
}
/// <summary>
/// 根據評分獲取描述
/// </summary>
public static string GetQualityDescription(int quality)
{
return quality switch
{
1 => "完全不記得",
2 => "有印象但錯誤",
3 => "困難但正確",
4 => "猶豫後正確",
5 => "輕鬆正確",
_ => "無效評分"
};
}
/// <summary>
/// 計算掌握度百分比
/// </summary>
public static int CalculateMastery(int repetitions, float easinessFactor)
{
// 基於重複次數和難度係數計算掌握度 (0-100)
var baseScore = Math.Min(repetitions * 20, 80); // 重複次數最多貢獻80分
var efficiencyBonus = Math.Min((easinessFactor - 1.3f) * 16.67f, 20f); // 難度係數最多貢獻20分
return Math.Min((int)Math.Round(baseScore + efficiencyBonus), 100);
}
}
/// <summary>
/// 複習優先級計算器
/// </summary>
public static class ReviewPriorityCalculator
{
/// <summary>
/// 計算複習優先級 (數字越大優先級越高)
/// </summary>
public static double CalculatePriority(DateTime nextReviewDate, float easinessFactor, int repetitions)
{
var now = DateTime.Today;
var daysDiff = (now - nextReviewDate).Days;
// 過期天數的權重 (越過期優先級越高)
var overdueWeight = Math.Max(0, daysDiff) * 10;
// 難度權重 (越難的優先級越高)
var difficultyWeight = (3.8f - easinessFactor) * 5;
// 新詞權重 (新詞優先級較高)
var newWordWeight = repetitions == 0 ? 20 : 0;
return overdueWeight + difficultyWeight + newWordWeight;
}
/// <summary>
/// 獲取應該複習的詞卡
/// </summary>
public static bool ShouldReview(DateTime nextReviewDate)
{
return DateTime.Today >= nextReviewDate;
}
}