lowkey-dolphinsr
v0.0.4
Published
Spaced repetition API for JavaScript
Downloads
6
Readme
DolphinSR: Spaced Repetition in JavaScript
DolphinSR implements spaced repetition in JavaScript. Specifically, it uses Anki's modifications to the SM2 algorithm including:
- an initial mode for learning new cards
- a mode for re-learning cards after forgetting them
- reducing the number of self-assigned ratings from 6 to 4
- factoring lateness into card scheduling
- Anki's default configuration options
While DolphinSR is intentionally very similar to Anki's algorithm, it does deviate in a few ways:
- improved support for adding reviews out of order (for example, due to network latency)
- very different internal data structures (DolphinSR is largely written in a functional style to make testing and debugging easier, and does not rely on storing computed data or any SQL database)
- only one kind of card
Installation
DolphinSR is an npm
package. Install it with either yarn add dolphinsr
or
npm install --save dolphinsr
.
It's strongly recommended that you use Flow to statically check your code when using DolphinSR. We rely on it exclusively for type-checking, and don't do any runtime validation of type arguments. For more information, visit the Flow webpage;
Quick Start
import { DolphinSR, generateId } from 'dolphinsr';
import type { Master, Review } from 'dolphinsr';
// Specify the combinations DolphinSR should make out of your master cards.
// Numbers refer to indexes on the card. (Don't worry and keep reading if you don't understand)
const chineseCombinations = [
{front: [0], back: [1, 2]},
{front: [1], back: [0, 2]},
{front: [2], back: [0, 3]}
];
const frenchCombinations = [
{front: [0], back: [1]},
{front: [1], back: [0]}
];
// Create the master cards that DolphinSR will use spaced repetition to teach.
// Note: in a real program, you'd want to persist these somewhere (a database, localStorage, etc)
const vocab: Array<Master> = [
{
id: generateId(),
combinations: chineseCombinations,
fields: ['你好', 'nǐ hǎo', 'hello']
},
{
id: generateId(),
combinations: chineseCombinations,
fields: ['世界', 'shìjiè', 'world']
},
{
id: generateId(),
combinations: frenchCombinations,
fields: ['le monde', 'the world']
},
{
id: generateId(),
combinations: frenchCombinations,
fields: ['bonjour', 'hello (good day)']
}
];
// Create the datastore used to house reviews.
// Again, in a real app you'd want to persist this somewhere.
const reviews: Array<Review> = [];
// Create a new DolphinSR instance
const d = new DolphinSR();
// Add all of your vocab to the DolphinSR instance
d.addMasters(...vocab);
// Add any existing reviews to the DolphinSR instance
// (In this example, this doesn't do anything since reviews is empty.)
d.addReviews(...reviews);
// Now, DolphinSR can tell us what card to review next.
// Since generateId() generates a random ID, it could be any of the cards we added.
// For example, it could be:
// {
// master: <Id>,
// combination: {front: [0], back: [1, 2]},
// front: ['你好'],
// back: ['nǐ hǎo', 'hello']
// }
const card = d.nextCard();
// It will also give us statistics on the cards we have:
// Since we added 2 masters with 3 combinations (the Chinese vocab) and 2 masters with 2
// combinations (the French vocab), we will have 10 cards. Since we haven't reviewed any of them
// yet, they will all be in a "learning" state.
const stats = d.summary(); // => { due: 0, later: 0, learning: 10, overdue: 0 }
// Now, we can review the current card (probably triggered by a real app's UI)
// If we already knew the answer, we would create a review saying that it was "easy" to recall:
const review: Review = {
// identify which card we're reviewing
master: d.nextCard().master,
combination: d.nextCard().combination,
// store when we reviewed it
ts: new Date(),
// store how easy it was to remember
rating: 'easy'
};
reviews.push(review); // in a real app, we'd store this persistently
d.addReviews(review);
// Since we reviewed the current card, and marked it easy to remember, DolphinSR will move it into
// 'review' mode, which resembles classic SM2 spaced repetition. So everything else will still be in
// 'learn' mode, and it will be scheduled to be reviewed later.
d.summary(); // => { due: 0, later: 1, learning: 9, overdue: 0 }
// This will show the next card to review.
d.nextCard();
API
generateId(): Id
This generates a new ID for a master card. It uses the uuid
package under the hood. Always use
generateId()
to generate IDs for your masters.
new DolphinSR()
Create a new DolphinSR instance, d
.
(new DolphinSR()).addMasters(...masters: Master[]): void
Add masters to the DolphinSR instance. Masters with duplicate IDs will cause a runtime exception.
(new DolphinSR()).addReviews(...reviews: Review[]): boolean
Add reviews to the DolphinSR instance.
addReviews()
is significantly more efficient if reviews
are sorted in ascending order by ts
,
and all chronologically come after the previous latest review for any card. If this condition is
met, addReviews()
will return false
. Otherwise, it returns true
.
(new DolphinSR()).summary(): { due: number, later: number, learning: number, overdue: number }
Returns summary statistics for cards. Each category (due, later, learning, overdue) is a count.
- Due cards are cards that the algorithm has determined should be reviewed on the day of the call.
(That is, the current date as reflected by
new Date()
.) - Overdue cards are cards that the algorithm has determined should be reviewed earlier than the day of the call.
- Learning cards are cards that are either new and don't have any reviews, or cards that were
forgotten (reviewed with an
again
rating) and not yet re-learned. - Later cards are cards that will be due in the future.
(new DolphinSR()).nextCard(): ?Card
Returns the next card to be reviewed, or null
if there are no cards that need to be reviewed. Any
card classified as due, overdue or learning by .summary()
could appear in .nextCard()
.
Note: .nextCard()
is deterministic--there is a defined order for prioritizing cards that are in
different classifications and within classifications. That means that for any given set of masters
and reviews added to a DolphinSR instance, nextCard()
will return the same result.
Types
Master
A Master
is an object conforming to the following Flow signature:
{
id: Id, // from generateId()
fields: Array<Field>, // see Field
combinations: Array<Combination>, // see Combination
}
Field
A Field
is a unit of data in a master. It is type alias for string
.
Combination
A Combination
is an object representing how Fields
in a master should be combined to fit on a
card with a front and a back. It looks like this:
{front: number[], back: number[]}
Rating
A Rating
is an enum describing how well a user knows a specific combination of a master card. It
can be (in descending order of ease):
'easy'
'good'
'hard'
'again'
Review
A Review
is an object describing a review of a card by a user. It should look like this:
{
master: Id,
combination: Combination,
ts: Date,
rating: Rating,
}
Card
A Card
is an object returned by .nextCard()
which describes a part of a master suitable for
displaying to a user. It should look like this:
{
master: Id,
combination: Combination,
front: Array<Field>,
back: Array<Field>
}