A database of quiz questions to generate quizzes and solutions

| categories: education | tags:

In this post I talked about a machine gradable quiz. One issue I have been trying to resolve is how to keep questions and solutions together. Currently, I have them separated, and that occasionally leads to them not being synchronized. One tricky part is if they are together, how do you create the quiz without answers in them? The answer is you have to "export" the quiz and selected problems, with selective content, and you want the elements of the question exported in some format, e.g. html or LaTeX.

I am going to explore representing a question as some data structure, not in org-mode for now. Then, we will use emacs-lisp to format the question the way we want it. The data we need about a question include the type of question, a unique id, the question, the choices, and the answer. For the choices, we might want some descriptive text for the solution. Here is one example of how we could represent a question as a struct in emacs-lisp. I do not claim here that this is the best or most complete way to represent it; it is simply a way that we could do it so we can discuss it later.

(defstruct question type id question choices)
question

The defstruct macro creates a constructor function "make-question" for us, and we create questions like this:

(setq q1 (make-question
          :type 'multiple-choice
          :id "id:1"
          :question "What is 2 + 2?"
          :choices '(('choice 0 "Not correct")
                     ('choice 2 "Not correct")
                     ('choice 4 nil 'answer)
                     ('choice "None of the above" "Not correct"))))
[cl-struct-question multiple-choice "id:1" "What is 2 + 2?" (((quote choice) 0 "Not correct") ((quote choice) 2 "Not correct") ((quote choice) 4 nil (quote answer)) ((quote choice) "None of the above" "Not correct"))]

The defstruct macro also creates accessor functions to get the information stored in the question. These go by "question-SLOTNAME" and they make it pretty easy to get the data. We can convert this structure to an org-mode formatted question by using a formatted string, substituting values from the question object where we want them. I will make the headline contain the question, create some properties, and then make an org-link for each choice. The mc link is something I defined in the previous post.

(format "** %s
  :PROPERTIES:
  :ID: %s
  :END:
%s" (question-question q1)
(question-id q1) 
(mapconcat 'identity (loop for i to (length (question-choices q1))
                           for choice in (question-choices q1)
                           collect
                           (format "[[mc:%s]] %s" i (elt choice 1)))
           "\n"))
** What is 2 + 2?
  :PROPERTIES:
  :ID: id:1
  :END:
[[mc:0]] 0
[[mc:1]] 2
[[mc:2]] 4
[[mc:3]] None of the above

To generate a quiz, we would just need to loop over several questions and insert the strings into a buffer then save it to a file.

To generate the key, we use pretty similar code, and additionally find the choice that is the answer, and print any hints with the choices.

(format "** %s
  :PROPERTIES:
  :ID: %s
  :CORRECT-ANSWER: %s
  :END:
%s" (question-question q1)
    (question-id q1)
    ;; here we loop through the choices looking for the answer 
    (loop for choice in (question-choices q1)
          until (-contains? choice '(quote answer))
          finally return (elt choice 1))
    (mapconcat 'identity (loop for i to (length (question-choices q1))
                               for choice in (question-choices q1)
                               collect
                               (format "[[mc:%s]] %s
%s" i (elt choice 1) (or (elt choice 2) "")))
               "\n"))
** What is 2 + 2?
  :PROPERTIES:
  :ID: id:1
  :CORRECT-ANSWER: 4
  :END:
mc:0 0
Not correct
mc:1 2
Not correct
mc:2 4

mc:3 None of the above
Not correct

It is a similar process to convert to LaTeX or HTML as well, just different syntax in the format statement.

The next question is how to make authoring the questions pretty easy, i.e. with a minimal amount of syntax that represents the question data and is easy to transform. The lisp data structure is not too bad, but might be less familiar to many. Next we consider how to represent this question in org-mode. For example the question would be in a headline, and the ID and answer stored as properties. The choices might be stored as sub-headings that are tagged as choices. Then, constructing the question would entail going to the question headline, getting the information you want, and formatting it like I did above. The next section represents a question in org-mode. I think it actually takes more typing to do this, but some of it reuses some org-machinery I am familiar with. In any case, it is possible to store all the same kind of information using combinations of tags and properties. The next section is the question (only really visible in org-mode, not in the html post), and the section after that is how to use it.

1 What is 2 + 2?   multiple_choice

1.1 0   choice

Not correct, This is the answer to 2 - 2.

1.2 2   choice

Not correct

1.3 4   choice answer

Correct.

1.4 None of the above   choice

This is not correct

2 Working with the org representation of a question

Now, we use the org-machinery to work with the question and represent it in different formats. Here we just get the choices for a question with a specific id. We jump to the question, find headings tagged choice, and collect them.

(save-excursion
  (org-open-link-from-string "id:99D480BD-9651-4987-9646-4BE90BA6CAEC")
  (save-restriction
    (org-narrow-to-subtree)
    (let ((choices '()))
      (org-map-entries
       (lambda ()
         (add-to-list
          'choices
          (org-heading-components)
          t))
       "choice")
      choices)))
3 3 nil nil 0 :choice:
3 3 nil nil 2 :choice:
3 3 nil nil 4 :choice:
3 3 nil nil None of the above :choice:

You could use the headline for each choice to construct a question. For example, here we output some HTML to represent checkboxes in an HTML form.

(save-excursion
  (org-open-link-from-string "id:99D480BD-9651-4987-9646-4BE90BA6CAEC")
  (let ((all-choices 
         (save-restriction
           (org-narrow-to-subtree)
           (let ((choices '()))
             (org-map-entries
              (lambda ()
                (add-to-list
                 'choices
                 (elt (org-heading-components) 4)
                 t))
              "choice")
             choices)))
        (question (elt (org-heading-components) 4))
        )
    (concat
     "<html><body>\n"
     "<h1>" question "</h1>\n"
     "<form action = \"\">"
     (mapconcat
      'identity
      (loop for choice in all-choices
            collect       
            (format "  <input type=\"checkbox\" name=\"choice\" value=\"%s\">%s<br>"
                    choice choice))
      "\n")
     "\n</form>\n"
     "</body></html>")))

What is 2 + 2?

0
2
4
None of the above

There is no action button to submit this answer, but you could add one if wanted. Clearly, you will have custom functions to export each format you want, with the information you want in it. The code for the org-mode representation is a little more verbose than the lisp data representation, but they accomplish basically the same thing. One thing that would be necessary to do is create some functions that take a list of question IDs and generate the quiz and solution in a buffer, and a helm interface for question selection would be pretty sweet.

I can see adding several other pieces of information about a problem, including categories or keywords, level of difficulty, how many points the problem is worth, last time it was assigned, relationship to course learning objectives, maybe information about who authored the problem, and a source if that is appropriate. These would allow you to consider machine grading, or correlating performance between problems, assessing how a class is doing on a problem, etc… Lots of potential here.

Copyright (C) 2014 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.7c

Discuss on Twitter