0.0.4 • Published 2 years ago

lowkey-dolphinsr v0.0.4

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

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>
}
0.0.4

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago