* A database of quiz questions to generate quizzes and solutions :PROPERTIES: :categories: education :date: 2014/11/28 14:41:45 :updated: 2014/11/28 14:41:45 :END: In this [[http://kitchingroup.cheme.cmu.edu/blog/2014/11/23/Machine-gradable-quizzes-in-emacs+org-modex/][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. #+BEGIN_SRC emacs-lisp (defstruct question type id question choices) #+END_SRC #+RESULTS: : question The defstruct macro creates a constructor function "make-question" for us, and we create questions like this: #+BEGIN_SRC emacs-lisp (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")))) #+END_SRC #+RESULTS: : [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. #+BEGIN_SRC emacs-lisp (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")) #+END_SRC #+RESULTS: : ** 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. #+BEGIN_SRC emacs-lisp :results value org (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")) #+END_SRC #+RESULTS: #+BEGIN_SRC org ,** 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 #+END_SRC 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. ** What is 2 + 2? :multiple_choice: :PROPERTIES: :CORRECT-ANSWER: 4 :ID: 99D480BD-9651-4987-9646-4BE90BA6CAEC :END: *** 0 :choice: Not correct, This is the answer to 2 - 2. *** 2 :choice: Not correct *** 4 :choice:answer: Correct. *** None of the above :choice: This is not correct ** 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. #+BEGIN_SRC emacs-lisp (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))) #+END_SRC #+RESULTS: | 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. #+BEGIN_SRC emacs-lisp :results html (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 "
\n" "