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;
///
/// 計算下次複習的間隔和參數
///
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
);
}
///
/// 更新難度係數
///
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);
}
///
/// 獲取初始參數(新詞卡)
///
public static SM2Input GetInitialParameters()
{
return new SM2Input(
Quality: 3,
EasinessFactor: INITIAL_EASINESS_FACTOR,
Repetitions: 0,
IntervalDays: 1
);
}
///
/// 根據評分獲取描述
///
public static string GetQualityDescription(int quality)
{
return quality switch
{
1 => "完全不記得",
2 => "有印象但錯誤",
3 => "困難但正確",
4 => "猶豫後正確",
5 => "輕鬆正確",
_ => "無效評分"
};
}
///
/// 計算掌握度百分比
///
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);
}
}
///
/// 複習優先級計算器
///
public static class ReviewPriorityCalculator
{
///
/// 計算複習優先級 (數字越大優先級越高)
///
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;
}
///
/// 獲取應該複習的詞卡
///
public static bool ShouldReview(DateTime nextReviewDate)
{
return DateTime.Today >= nextReviewDate;
}
}