Pursuit of the Ideal Flash Card Program

The Need

At one point in my life, I was studying for the New York State bar exam. I had boiled down a great quantity of material into simple question-and-answer format, all contained within a single large text file. Each question began with an asterisk; its answer followed a paragraph break; and then the next question would begin, like this:

*This would be a question.

Here is its answer.

*Then another question.

Followed by its answer.

This was the form of the data file. It was easy to assemble. Well, of course, it was an incredible ordeal to assemble, as I plowed my way through those law review books, converting their contents into question-and-answer format, but at least the structure of the data files was straightforward.

Once I had all that data, I needed a flashcard program that would show me a randomly selected question from among my thousands of entries. The program would wait for me to think about the answer, and then let me hit a key to see the answer. The answers to these questions could include quite a few words, so I didn’t want to have to type them out; I just wanted to get an answer into my mind and then see if I had it right.

Upon viewing the answer, I had to decide whether I knew the answer or should review it later. Ideally, I would need just one hand to hit, say, the left arrow key for a retry or the right arrow key to put that question into the “finished” stack. If the question went back into the “retry” stack, it would rejoin the other questions that I hadn’t yet mastered, and would come up again at random.

That is more or less what I needed. I got lucky, there in one of the computer rooms at Columbia, in fall 1982: there was a Pascal programmer named Michael Rubin who was willing to invent this program for me. He called it Tester. He stayed up half the night perfecting it, and then told me that this was foolish as he was scheduled to take the GRE the next morning. Talk about feeling guilty. I have always hoped he did well.

For me, that guy’s little Tester program was a lifesaver. It was incredibly fast – I could go through questions and answers almost at reading speed. I used it for countless hours of law review. My record: one time, I sat there for 22 hours straight, taking breaks only for the bathroom and to run over to Ta-Kome Foods for another sandwich or coffee. The program made it so easy to just sit there and learn.

Over the years since then, there have been many occasions when I could have used a program like that, when I had some things to memorize and wished I had something so easy and helpful. I looked at many flashcard programs, but the ones I saw all seemed clunky in various ways: they wanted me to type out the answers, or they involved too many keystrokes, or the data input was a hassle, or they were too expensive, or for whatever reason they didn’t compare.

Tester: Source Code

For posterity, here is the source code of Michael Rubin’s Tester program:

{$D-}
 {Program by Michael Rubin, 11 December 1982}
 {Reads a file containing questions (starting with '*') followed by answers, all
 separated by blank lines, and asks questions randomly in flash-card fashion}
 
 PROGRAM tester(input);
 CONST maxques = 2000; strlen = 132;
 TYPE string = record
           data: packed array[1..strlen] of char;
           len: integer
         end;
 VAR ques, ans: array[1..maxques] of integer;
     numques, quesleft, q: integer;
     s:string;
     ch: char;
 
 PROCEDURE print(s:string); var i:integer; begin
   for i:=1 to s.len do write(tty,s.data[i]); writeln(tty)
 end;
 
 
 PROCEDURE startup; var s:string; i,q:integer;
 begin
 q:=0;
 repeat
   readln(s.data:s.len);
   if not(eof(input)) and (s.len>0) and (s.data[1]='*') then begin {question}
     q:=q+1;
     ques[q]:=curpos(input)-s.len-3; {point to beginning of line I just read}
   end {if}
 until eof(input);
 numques:=q;
 end; {procedure}
 
 
 PROCEDURE clear; var i:integer; begin for i:=1 to 24 do writeln(tty) end;
 
 
 BEGIN {main}
 startup;
 quesleft:=numques;
 
 repeat {ask a question}
   repeat
     q:=1+trunc(numques*random(0)); {random no. between 1 and numques}
   until ques[q]>-1;        {don't select a killed question}
   setpos(input,ques[q]);
   clear;
   loop    {print question}
     readln(s.data:s.len);
   exit if s.len=0;
     print(s)
   end;
   readln(tty); {user hits return}
   writeln(tty);
   loop {print answer}
     readln(s.data:s.len);
   exit if s.len=0;
     print(s)
   end;
   writeln(tty);
   write(tty,'Type 1 if you got it right, 0 if wrong, or 2 to leave program: ');
   readln(tty); read(tty,ch);
   if ch='1' then {kill that question}
     begin ques[q]:=-1; quesleft:=quesleft-1 end;
 until (ch='2') {exit signal} or (quesleft=0);
 writeln(tty,'Goodbye.');
 END.

I wasn’t able to figure out how to make that program work again. In a discussion, Icepickle produced a C# version:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace ConvertedPascalQuestions
{
    class Program
    {
        class QuizItem
        {
            public string Question { get; set; }
            public string Answer { get; set; }
            public string AnsweredWith { get; set; }
            public bool Asked { get; set; }

            public bool Correct
            {
                get
                {
                    return Asked && string.Equals(Answer, AnsweredWith, StringComparison.OrdinalIgnoreCase);
                }
            }

            public QuizItem(string question, string answer)
            {
                Question = question;
                Answer = answer;
                AnsweredWith = string.Empty;
                Asked = false;
            }
        }

        class Quiz : IDisposable
        {
            private readonly IList questions = new List();
            public virtual IList Questions
            {
                get
                {
                    return questions;
                }
            }

            public string Filename { get; protected set; }

            public Quiz(string filename)
            {
                this.Filename = filename;
                Load();
            }

            protected virtual void Load()
            {
                if (string.IsNullOrWhiteSpace(Filename))
                {
                    throw new ArgumentException("Filename was not specified!");
                }
                if (!File.Exists(Filename))
                {
                    throw new ArgumentException("The filename you specified wasn't found back on the filesystem!");
                }
                try
                {
                    using (FileStream fs = new FileStream(this.Filename, FileMode.Open, FileAccess.Read))
                    {
                        using (TextReader tr = new StreamReader(fs))
                        {
                            string all_text = tr.ReadToEnd();
                            string[] lines = all_text.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                            Questions.Clear();
                            for (var i = 0; i < lines.Length; i += 2)                             {                                 if (i + 1 >= lines.Length)
                                {
                                    // not a pair, no read
                                    continue;
                                }
                                var str = new QuizItem(lines[i], lines[i + 1]);
                                Questions.Add(str);
                            }
                        }
                    }
                    Console.WriteLine("Loaded {0} question(s) from {1}!", Questions.Count, Filename);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Couldn't read file, due to: {0}\r\n{1}", ex.Message, ex.StackTrace);
                    throw;
                }
            }

            public bool ContinueWithNextQuestion()
            {
                bool emptyResponse = false;
                int available = Questions.Count(i => !i.Asked);
                int random = new Random().Next(available);
                int index = 0;
                string answer = string.Empty;

                QuizItem qItem = Questions.First(i => !i.Asked ? index++ == random : false);

                if (qItem == null)
                {
                    return false;
                }

                Console.Write("* {0}\r\nYour answer: (press Enter to exit) ", qItem.Question);
                answer = Console.ReadLine();
                if (string.Equals(answer, string.Empty))
                {
                    emptyResponse = true;
                }
                else
                {
                    qItem.Asked = true;
                    qItem.AnsweredWith = answer;
                    if (qItem.Correct)
                    {
                        Console.WriteLine("Your answer was correct!");
                    }
                    else
                    {
                        Console.WriteLine("Incorrect answer, the correct answer was: {0}", qItem.Answer);
                    }
                }

                return Questions.FirstOrDefault((qi) => !qi.Asked) != null && !emptyResponse;
            }

            public void Dispose()
            {
                questions.Clear();
            }

            public int Correct
            {
                get
                {
                    return Questions.Count(i => i.Correct);
                }
            }

            public int Asked
            {
                get
                {
                    return Questions.Count(i => i.Asked);
                }
            }

            public double Percentage
            {
                get
                {
                    return Asked == 0 ? 0 : (Correct / (double)Asked) * 100d;
                }
            }
        }

        static void Main(string[] args)
        {
            string filename = string.Empty;
            foreach (string arg in args)
            {
                if (!File.Exists(arg))
                {
                    continue;
                }
                filename = arg;
                break;
            }
            if (string.IsNullOrWhiteSpace(filename))
            {
                filename = "./question-resource.txt";
            }

            Quiz myQuiz = new Quiz(filename);

            while (myQuiz.ContinueWithNextQuestion())
            {
                //
            }
            Console.WriteLine("You have completed the quiz!\r\nYou have replied correct to {0} of {1} questions ({2:n2} %)!\r\nPress  to exit...",
                myQuiz.Correct, myQuiz.Asked, myQuiz.Percentage);

            Console.ReadLine();
        }
    }
}

But I still had no clue on how to compile, much less to revise, such a program.

Searching for Flashcard Programs

Eventually, I decided to see if maybe some other flashcard program would do what I wanted. It seemed that a modern update would do things that Michael’s old Tester program didn’t: it might remember where I had left off, and which questions were in which stack; it could have more stacks; it could provide real-time reports on how well I was doing; and so forth. Such improvements would be nice. Ideally, the program would come with source code, preferably simple enough that I could figure out how it worked and could alter it as needed. But the core issue at this point was just to find a Tester equivalent.

I thought I might begin by seeing what others were using and recommending. A couple of searches led to a Datamation list naming FlashQard, jVLT, Mnemosyne, Pauker, Parley, and Anki flashcard programs. AlternativeTo named Anki, Memrise, and Mnemosyne, in order of declining popularity, along with several language-specific programs (e.g., Duolingo, Rosetta Stone). In addition, Wikipedia offered a list of flashcard programs, with information on the platforms available and so forth. Browsing through assorted discussions led to the impression that Anki and Mnemosyne were the leading contenders in the all-purpose flashcard program category.

Between those two, it appeared that, in the last several years, Anki had become more sophisticated, powerful, and also complicated, while Mnemosyne tended to emphasize simplicity. It appeared that both programs allowed for considerable customization through use of add-ons or plug-ins. There were apparently hundreds of “decks” of flashcards that various people had prepared on assorted subjects, including languages, sciences, and other topics. It looked like there were far more of these canned decks available in Anki than in Mnemosyne, though preliminary browsing suggested that it might be feasible to import Anki decks into Mnemosyne. On a related note, I felt it was sometimes beneficial to prepare my own flashcards, insofar as doing so could require me to think about and articulate aspects of the subject under study.

I decided to download and run both Anki and Mnemosyne. I was using Windows 7 x64. The Anki installer indicated that it would need 80.3MB of disk space; Mnemosyne’s indicated 86.3MB. When I ran the programs, Anki waited for action from me, while Mnemosyne introduced me to the spaced repetition system (SRS) concept that both programs implemented. As described by Wikipedia, SRS meant that difficult material would appear more frequently than easy material. In Mnemosyne’s implementation, I should give a card a score of 5 if it was too easy, 4 if the passage of time since my last view of the card required me to make just the right amount of effort to remember it, 2 or 3 if the card was too hard (i.e., if I should have been seeing it more frequently), and 0 or 1 if I failed to remember it at all. I assumed Anki had some similar arrangement. On brief investigation, this did not appear to be a mere fad: the first research study I looked at agreed that, within its scope, spaced learning was better than massed learning.

I didn’t have any cards, so I decided to hunt for some. For now, I thought it might be easiest to use a deck of cards already set up by someone else. Following an Anki video, I went to Anki’s “Get Shared” button (bottom left). That opened an Ankiweb page. I searched the Anki webpage for “algebra.” That gave me a list of about 20 decks. At Mnemosyne, by contrast, there were only four math decks of any kind. Given the potential difficulty of typing math formulas into a program, it seemed that, for at least some purposes, I would probably download Anki decks and then import them into Mnemosyne, if that was possible.

On closer inspection, I found that few if any of those 20 decks were relevant to what I had in mind when I selected algebra. I was thinking of basic college algebra. These were for linear algebra, abstract algebra, algebra acoustics; several were in German. Moreover, when I viewed the samples, I couldn’t tell what they really looked like; it appeared that formatting codes had been converted into ordinary text, basically making a mess. I finally chose one, clicked its Download button, went with the default option (i.e., a dialog popped up and asked if I wanted to download or open the deck, and I said OK to the default “open” option) — and, presto! the cards were imported into Anki. That was pretty smooth.

So now I was looking at one of those algebra cards. It showed me a question and waited for me to click on the Show Answer button. I wondered if there was an option of using a key instead of the mouse, as in my old Tester program (above). Anki’s online manual said,

When the answer is shown, you can use space or enter to select the Good button. You can use the 1-4 keys to select a specific ease button. Many people find it convenient to answer most cards with space and keep one finger on 1 for when they forget.

Another place in the manual explained those words. Similar to Mnemosyne, I could choose to mark a card with a 1 (short for Again, meaning that my answer was incorrect and I needed to see the card more frequently in the future), 2 (for Hard, which the manual described confusingly, but the idea seemed to be that this would schedule the card more frequently), 3 (for Good, i.e., an appropriate amount of time has passed since the last time I saw this card), or 4 (Easy, meaning that I got it right and don’t need to see it again for a while). There was also an option to use spacebar or Enter to move through the deck rapidly, not changing anything.

This raised a question. Just how frequently was Anki going to be showing me these cards? Because when I was learning new items, I didn’t want to be waiting for days until they popped up again. I might need to go over them repeatedly to get some initial learning. The manual distinguished between Review (described in the preceding paragraph) and Learning the items for the first time, in which they would apparently come up again within just a few minutes.

It seemed I might be best advised to just try it out and see how it worked. So now that I knew I could space through the deck, I did that. As I went through this algebra deck for the first time, I saw that Anki was showing me buttons for Again, Good, and Easy, and was also indicating, above those buttons, the amount of time that would elapse until the next time I would see the card: <1m (i.e., minute) for Again, <10m for Good (i.e., spacebar), and 4d (days) for Easy.

I decided I didn’t much like the algebra deck I had downloaded. I had figured they would have incorporated some formulas, to give me a hint on how graphic representations would look, but such was not the case. After glancing at a couple other decks, I decided to try creating some cards for myself. Anki offered an Add option. I went into that, clicked the paperclip button at upper right, and navigated to an image file. The file was imported, but what if I wanted to ask a question (e.g., “What does this picture represent?”)? It seemed that I would have to compose the whole thing, question plus picture, in something like Microsoft Word, and then save the product as an image file (possibly via PrintScreen or other screen capture) to be imported into Anki. I knew Word had an equation editor and an ability to add all sorts of funky characters, so probably it would be my first stop for something like that.

So far, Anki was not seeming very complicated. I wondered if the complaints about complexity just meant that Anki offered a lot of options. I thought maybe I should try exporting my Anki deck, with this newly added image card, and import it into Mnemosyne and see how that worked. Anki was willing to export as an Anki Deck Package (*.apkg) or in plain text. The latter would lose my image card. But Mnemosyne did not appear able to import in .apkg format. There was perhaps an add-on to Anki or to Mnemosyne that would broaden the import-export possibilities. But for the moment, I was not inclined to investigate that.

Well, if I was going to have to create my own cards, what about creating them in Mnemosyne, and then optionally exporting them to Anki? Anki did offer an ability to import a Mnemosyne deck; I felt that they should have offered an export option in that format as well. Be that as it may, how would I create an image card in Mnemosyne? The Wikipedia list of flashcard programs said that Mnemosyne did have the ability to display images. But how? I was not seeing any such option in its menus. Maybe that capability was to be added via a plugin.

As I fiddled with that thought, I idly clicked to import one of the four decks that Mnemosyne’s webpage did offer. And, well, what do you know, it appeared that this deck might cover a spectrum of undergraduate math concepts, in a much more comprehensive way than those little decks I was fooling with at Ankiweb. I couldn’t be sure, though, because when I tried to view this comprehensive deck in Mnemosyne, I got an error message: “Problem with latex. Are latex and dvipng installed?” It seemed I would need to figure out how to install the LaTeX document preparation system in Mnemosyne to use this deck.

A Closer Look

At this point, I found that the need du jour — for a flashcard program on which I could present mathematical symbols — was best met by simply using old-fashioned 3″ x 5″ index cards. Later, though, when I needed to do some memorization that was largely text-based, I returned to try Anki again. At this point, it was Anki 2.0.32.

Unfortunately, I quickly found that Anki could import only a few formats (Anki Deck, Mnemosyne, Supermemo, Pauker, and tab- or semicolon-delimited text). If I wanted to prepare my flashcards in Microsoft Word with basic formatting (e.g., bulleted lists of factors that might apply to a quiz problem), it appeared that I would have to go elsewhere. But then I found a discussion indicating that quotation marks might provide a solution.

This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s