Skip to content

Instantly share code, notes, and snippets.

@doctorpangloss
Created July 9, 2018 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doctorpangloss/928cf677fbe8de34ae80cd539c65fce9 to your computer and use it in GitHub Desktop.
Save doctorpangloss/928cf677fbe8de34ae80cd539c65fce9 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ProjectHolste
{
public class RepetitionAlgorithm<T> : IEnumerator<T>
{
public Func<T, string> Keyer { get; private set; }
private readonly TimeSpan m_TimeSinceLastSession;
private readonly List<T> m_Problems = new List<T>();
private bool m_Answered = false;
public RepetitionAlgorithm(TimeSpan timeSinceLastSession, Func<T, string> keyer, List<T> problems)
{
Keyer = keyer;
m_TimeSinceLastSession = timeSinceLastSession;
// Choose N number of items for review minimum, ordered by descending PercentageOverdue
m_Problems.AddRange(problems.OrderByDescending(SortScore));
// Discard items reviewed in the past 8 hours
}
private float SortScore(T arg)
{
var record = this[Keyer(arg)];
if (DateTime.Now - new DateTime(record.DateLateReviewed) > TimeSpan.FromHours(8))
{
return PercentOverdue(record);
}
return 0f;
}
public RepetitionAlgorithmRecord this[string id]
{
get
{
var json = PlayerPrefs.GetString(id, null);
if (json == null)
{
return new RepetitionAlgorithmRecord()
{
Key = id,
DateLateReviewed = DateTime.Now.Ticks,
Difficulty = 0.5f
};
}
return JsonUtility.FromJson<RepetitionAlgorithmRecord>(json);
}
set
{
PlayerPrefs.SetString(id, JsonUtility.ToJson(value));
PlayerPrefs.Save();
}
}
public void Answer(TimeSpan timeSpent, bool choseCorrectly)
{
var impliedDifficulty = 1f / (1f + Mathf.Exp(-0.4f * (float) timeSpent.TotalSeconds + 2f));
if (choseCorrectly)
{
impliedDifficulty = Mathf.Max(0.6f, impliedDifficulty);
}
if (Current == null)
{
throw new System.NotImplementedException();
}
var record = this[Keyer(Current)];
var percentOverdue = PercentOverdue(record, choseCorrectly);
var difficulty =
Mathf.Clamp01(record.Difficulty + percentOverdue * (1f / 17f) * (8f - 9f * impliedDifficulty));
var difficultyWeighting = 3f - 1.7f * difficulty;
var daysBetweenReviews = 1f / (difficultyWeighting * difficultyWeighting);
if (choseCorrectly)
{
daysBetweenReviews = 1 + (difficultyWeighting - 1f) * percentOverdue;
}
// Save record
record.DateLateReviewed = DateTime.Now.Ticks;
record.DaysBetweenReviews = daysBetweenReviews;
record.Difficulty = difficulty;
this[record.Key] = record;
}
private float PercentOverdue(RepetitionAlgorithmRecord record)
{
return PercentOverdue(record, record.LastChoseCorrectly);
}
private float PercentOverdue(RepetitionAlgorithmRecord record, bool choseCorrectly)
{
var percentOverdue = 1.0f;
if (choseCorrectly)
{
var totalDays = (float) (DateTime.Now - new DateTime(record.DateLateReviewed)).TotalDays;
percentOverdue = Mathf.Min(2.0f, totalDays / (float) m_TimeSinceLastSession.TotalDays);
}
return percentOverdue;
}
public bool MoveNext()
{
if (!m_Answered)
{
throw new UnityException("m_Answers == false");
}
m_Answered = false;
}
public void Reset()
{
throw new System.NotImplementedException();
}
public T Current { get; private set; }
object IEnumerator.Current
{
get { return Current; }
}
public void Dispose()
{
throw new System.NotImplementedException();
}
}
[Serializable]
public class RepetitionAlgorithmRecord
{
public long DateLateReviewed;
public float Difficulty;
public float DaysBetweenReviews;
public bool LastChoseCorrectly;
public string Key;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment