Introduction Study cards

Anki Books


Anki Books is a free and open-source software (see the AGPL-3.0 license) and proof-of-concept of online books featuring active practice and spaced repetition by integrating with Anki. 

How does Anki Books create Anki notes?


The first part of Anki Books that was developed was a module called Anki Record, a Ruby gem that creates Anki deck packages. Anki deck packages are the files with the .apkg extension that import into Anki and create or update Anki notes. By using Anki Record, Anki Books is able to take its data conforming to a different model and generate Anki deck package files to import into Anki.

Knowledge graphs in Anki Books


The Anki Books data can be downloaded as CSV files for study with network analysis tools like NetworkX and Gephi. The data structure is largely a tree which would not be interesting on its own, but there is a non-trivial structure due to the cloze deletion notes (fill-in-the-blank flashcards). Each fill-in-the-blank becomes a concept. A cloze deletion note with multiple concepts is a node with an edge to each concept. Visualization of the knowledge structure is a fun way to track your Anki learning habits and it is also useful to see what ideas have been studied from multiple sources. It is especially satisfying because studying the represented information with spaced repetition encodes and consolidates it into long-term memory. Memory naturally has an associative nature, and the mental representation overlaps with the physical representation in the database.

The image shows an early Anki Books graph with about a dozen clustered tree structures being pulled to the center by a simulated gravity. This article is represented in the very small group near the top left perimeter. Gephi was used for the layout and rendering of the graph (graph in this context meaning a structure of nodes connected by edges). This early graph was created when domains had a self-referential many-to-many relationship. I got rid of that after I accidentally introduced a recursive SQL query that may have caused a memory leak. In the following design, a domain could have only one parent domain. Cloze notes and concepts were not introduced until a lot later. Sometime after cloze notes and concepts were added, domains were removed after they were made redundant by allowing books to act as parents to other books. This simplification allowed removing over 1500 lines of code (commit SHA: f4d76edd49946ecca0873248bc4f76c3b82af4e1). The refactoring of basic notes and cloze notes into using a Single Table Inheritance design pattern simplified things later too but was a more involved effort.

Incremental reading in Anki Books


Anki Books can be used for incremental reading in a basic and limited way. There will definitely be improvements to the software in this aspect. Currently it uses a simple load-balancing algorithm: choose an article at random. Articles are categorized as reading, writing, and complete but this design will almost certainly be changed.

How does Anki Books create graphs?


Anki Books has endpoints for downloading CSV files of all the data that can be used to construct a graph that is similar to Wozniak's knowledge tree at the roots and Wozniak's concept network towards the remote areas especially where there are cloze notes. A cloze note with two concept cloze deletions is a node connecting the two concepts, for example.

This is an example Python script to create a file for Gephi:

// Programmer #1:
// Sorry about the font on this code
// TODO: fix font
// Programmer #2:
// Leave this comment as a warning to be wary in the code around here
import networkx as nx
import matplotlib.pyplot as plt
import csv

path = "./prod"

G = nx.Graph()

G.add_node("center")

with open(f"{path}/books.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    title = row[1]
    parent_book_id = row[2]
    G.add_node(node_id)
    G.nodes[node_id]["book"] = True
    G.nodes[node_id]["title"] = title
    if parent_book_id != "":
      G.add_edge(node_id, parent_book_id)
    else:
      G.add_edge(node_id, "center")

with open(f"{path}/articles.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    title = row[1]
    book_id = row[2]
    G.add_node(node_id)
    G.nodes[node_id]["article"] = True
    G.nodes[node_id]["title"] = title
    G.add_edge(node_id, book_id)

with open(f"{path}/basic_notes.csv", 'r') as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    front = row[1]
    back = row[2]
    article_id = row[3]
    G.add_node(node_id)
    G.nodes[node_id]["basic_note"] = True
    G.nodes[node_id]["front"] = front
    G.nodes[node_id]["back"] = back
    G.add_edge(node_id, article_id)

with open(f"{path}/concepts.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    name = row[1]
    G.add_node(node_id)
    G.nodes[node_id]["name"] = name

with open(f"{path}/cloze_notes.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    sentence = row[1]
    article_id = row[2]
    G.add_node(node_id)
    G.nodes[node_id]["sentence"] = sentence
    G.add_edge(node_id, article_id)

with open(f"{path}/cloze_notes_concepts.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    cloze_note_id = row[0]
    concept_id = row[1]
    G.add_edge(cloze_note_id, concept_id)

nx.draw_networkx(G, with_labels=False, node_size=5)
plt.savefig(f"{path}/basic_notes_graph.svg")
nx.write_graphml(G, f"{path}/basic_notes_graph.graphml")


Example: a small area of an Anki Books graph


The memory imprint of a book, in this case APSD, is represented here as a concept map in a larger network. The concepts of this book relate to software design and the causes of and manifestations of complexity in software. In dark mode (at least in the future, because in recent refactoring, dark mode was removed temporarily), the labels are hard to read, but the image shows part of a network (or "graph") with a tree structure where there are also labeled concept nodes adding a less trivial structure to the tree. From top-left to bottom-right, the labels are interface, cognitive load, module, implementation, change amplification, unknown unknowns, modular design, abstraction, complexity, obscurity, dependencies, Agile, problem decomposition, and tactical programming.

Why does Anki Books use Ruby on Rails?


I was working a lot with Ruby on Rails when I started work on (this third version of) Anki Books. Ruby is a gentle, quirky, and rapid programming language. Combine this with the power of Rails and it's a great choice if you like new Ruby virtual machines. Ruby is so gentle that it cannot stop some projects from going off the Rails (the CS guys I knew in college would say it's just writing English). Since Anki Books is free software that cannot be made proprietary, the gentleness and power of the Ruby on Rails stack will hopefully be ideal for anyone who wants to extend the functionality.

Why does Anki Books use PostgreSQL?


PostgreSQL is (one of) the finest and most advanced free and open-source object-relational database management software in the world.

Anki Books is licensed under the AGPLV3 (it is free software)

Example: an Anki Books article


ABC metric


The ABC metric is a measure of program size, but it may also measure complexity. It is a vector quantity with the number of assignments, branches, and conditionals in some code. This code may be a method, class, source file, or entire program. The ABC metrics can be added together linearly, but their magnitudes cannot, in the same way as is true for any vectors.

As determined by RuboCop, a static code analysis tool for Ruby, the following method has an ABC metric of <0, 6, 1>:

##
# Returns nth number in the Fibonacci sequence starting with 1, 1 ...
def fibonacci(n)
  [1, 2].include?(n) ? 1 : fibonacci(n - 1) + fibonacci(n - 2)
end

5.times do |i|
  puts fibonacci(i + 1)
end

There are no assignments so the first value of the vector is 0. Each function or method call adds a branch. Since Ruby is an "everything is an object" language, some of the method calls look like operators. There are two recursive calls to fibonacci, one call to include?, and three calls to methods which look like arithmetic operators. In a language where the arithmetic operators were not methods, this function might have a B value of 3 instead of 6.

By default, RuboCop complains about a method's ABC metric when its magnitude exceeds 17. The method can usually be refactored to eliminate this warning by extracting parts of it to smaller methods.

Cyclomatic complexity


Cyclomatic complexity is a measure of program complexity and focuses on the number of linearly independent paths through the program. The fibonacci method from the previous example has a cyclomatic complexity of 2 due to the ternary operator.
RuboCop will complain about this for the following method:

def triangle(a, b, c)
  if invalid_triangle?(a, b, c)
    :invalid
  elsif a == b && b == c && a == c
    :equilateral
  elsif a == b || b == c || a == c
    :isosceles
  else
    :scalene
  end
end

This method has a cyclomatic complexity of 8. The RuboCop documentation explains:

The algorithm counts decision points and adds one.

An if statement (or unless or ?:) increases the complexity by one. An else branch does not, since it doesn’t add a decision point. The && operator (or keyword and) can be converted to a nested if statement, and ||/or is shorthand for a sequence of ifs, so they also add one. Loops can be said to have an exit condition, so they add one.

There are 3 ifs and 4 logical operators adding to 7 decision points, so the cyclomatic complexity is 8. By default, RuboCop will complain about a method's cyclomatic complexity when it exceeds 7.

The Law of Demeter


The Law of Demeter, or principle of least knowledge, is a design guideline that was proposed at Northeastern University in 1987. It recommends that each unit should have limited knowledge about other units and only interact with units to which it is "closely" related. If an object calls a method on an object to only access a third object to call a method on it, then this law is not being followed in that case. Oftentimes this law can be stated as "use only one dot" but this rule is not an exact statement of the idea because the LoD is not always broken when there is more than one dot.
Examples notes [...].
The ABC metric was introduced as a possibly better alternative for what other software metric?
What software metric measures the number of linearly independent paths through a program?
Another [...].
Of cyclomatic complexity and ABC metric, which focuses more on program size?
What do A, B, and C stand for in the ABC metric?
What is the magnitude of an ABC metric <3,0,4>?
What is the ABC metric of this line of Ruby code? (x > 2) ? (y = 3) : (z = 5)
What is the smallest cyclomatic complexity that RuboCop will give a warning for by default?
What version of the name is System.Console?
What is the other name for the Law of Demeter?
Of cyclomatic complexity and ABC metric, which focuses more on program complexity?
Why does an else branch not add one to the cyclomatic complexity in the algorithm used by RuboCop?
Where was the Law of Demeter introduced?
The Law of Demeter is a specific case of:
What is a common statement explaining how to follow the Law of Demeter?