A hint system for problems in org-mode

| categories: org | tags:

I use org-mode to write problems for classes that I teach. Sometimes it is helpful to be able to provide hints about aspects of the problem. I have used drawers for that before. Here I will look at another approach. The idea is to store a unique id (org-id) in the problem headline. We will make hints somewhere else, and each hint will store the id they refer to in some property. We will run a command in the problem that finds the hints, and offers a menu you can select from.

In the next sections, we define a Problem statement, a section with hints in it, and finally code that defines a hint function.

1 Problem statement

What is the seventh number in the Fibonacci series?

2 Hints

Hints will be stored in headings somewhere. Here we put them in this file, but they could be stored in another file too. We give each hint a HINT property, with the problem id as the value. Here are three hints. In real example, the hints would not be this easy to find in the document. You might store them somewhere else, in another file for example.

2.1 What is the Fibonacci series?

The $ith number in the Fibonacci series is equal to the sum of the previous two numbers in the series.

2.2 What does the series start with?

The Fibonacci series starts with 1.

2.3 Example of the series.

The Fibonacci series goes as 1, 1, 2, 3, 5, 8, …

3 The hint code

We want to get the id from the problem the point is in, and then find hints for the problem. Then, we construct a menu and prompt the user to select a hint. I use a number to select the hint because it was easier to generate the menu that way. I like characters better, because you just have to press a key. With numbers you type the number and press enter. We open a new buffer with the contents of the hint in it. You can close the buffer by pressing q.

(defun hint ()
  "Present a menu of hints for the problem at point"
  (interactive)
  (let ((id (org-entry-get (point) "ID"))
        (entries '())
        (menu "")
        choice)

    (unless id
      (error "No problem ID found"))

    (org-map-entries
     (lambda ()
       (save-restriction
         (org-narrow-to-subtree)
         (add-to-list 'entries
                      (cons
                       (elt (org-heading-components) 4)
                       (buffer-string))
                      t)))
     (format "HINT=\"%s\"" id))

    ;; generate menu string
    (dolist (i (number-sequence 1 (length entries)))
      (setq menu (concat menu (format "[%s] %s\n" (- i 1)
                                      (car (elt entries (- i 1)))))))

    (setq choice (elt entries (read-number (concat menu "Your choice: ") 0)))
    ;; this feels a little clunky. Maybe I could just save a marker to
    ;; the headline, and open it in a narrowed indirect buffer.
    (when choice
      (switch-to-buffer "*hint*")
      (erase-buffer)
      (insert (cdr choice))
      
      (org-mode)
      (show-subtree)
      (setq buffer-read-only t)
      (use-local-map (copy-keymap org-mode-map))
      (local-set-key "q" #'(lambda () (interactive) (kill-buffer)))
      )))
hint

4 Summary

This seems like an interesting way to provide hints, or related information in org-mode. You could also consider using tags, or more sophisticated code to determine what else is relevant. For example, you might keep track of some performance metric, and use some heuristic algorithm that ranks the related information. Or perhaps fuzzy text searching, or combinations of criteria. If the number of hits got large, then the menu approach here might not be the best one. Then, something like the occur interface might be more suitable.

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