branch: externals/org-mathsheet
commit 4cfa9040d2a1bd9c178f94e0547547cf98d1e255
Author: Ian Martins <[email protected]>
Commit: Ian Martins <[email protected]>
Added math funcs, multi range sequences, worksheet instruction
---
example.org | 88 +++++++-------
mathsheet.org | 364 +++++++++++++++++++++++++++++++++++++++-------------------
2 files changed, 286 insertions(+), 166 deletions(-)
diff --git a/example.org b/example.org
index 3abbe060bd..19ea901393 100644
--- a/example.org
+++ b/example.org
@@ -9,58 +9,52 @@
| 1 | 3 | [a=2..10] - [1..$a] | small number subtraction, positive
result |
#+name: add-sub-2
-| weight | order | template | descr
|
-|--------+-------+-------------------------------+-----------------------------------------|
-| 3 | 1 | [1..10] + [0..10] | simple
|
-| 2 | 2 | [1..10] + [8..15] | second number bigger
|
-| 1 | 2 | [a=3..10] - [0..$a] | subtraction
|
-| 1 | 3 | [1..10] + [1..7] + [1..5] | three numbers
|
-| 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three with subtraction,
positive result |
-| 0 | 0 | [$a*[1..5]] / [a=1..10] | division
|
+| weight | order | template | descr
|
+|--------+-------+--------------------------------------+-----------------------------------------|
+| 3 | 1 | [1..10] + [0..10] | simple
|
+| 2 | 2 | [1..10] + [8..15] | second number bigger
|
+| 1 | 2 | [a=3..10] - [0..$a] | subtraction
|
+| 1 | 3 | [1..10] + [1..7] + [1..5] | three numbers
|
+| 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three with
subtraction, positive result |
+| 0 | 0 | [$a*[1..5]] / [a=1..10] | division
|
+| 5 | 5 | [a=1..10] + [b=0..10] - [0..($a+$b)] | three with
subtraction, positive result |
-#+BEGIN: problem-set :templates "add-sub-1" :count 21 :instruction "Compute
the answer"
-| problem | answer |
-|---------+--------|
-| 6 + 3 | 9 |
-| 1 + 6 | 7 |
-| 5 + 6 | 11 |
-| 2 + 0 | 2 |
-| 1 + 4 | 5 |
-| 8 + 7 | 15 |
-| 6 + 5 | 11 |
-| 2 + 3 | 5 |
-| 9 + 9 | 18 |
-| 3 + 8 | 11 |
-| 1 + 11 | 12 |
-| 4 + 2 | 6 |
-| 2 + 9 | 11 |
-| 1 + 4 | 5 |
-| 11 + 1 | 12 |
-| 6 + 0 | 6 |
-| 4 + 9 | 13 |
-| 7 + 10 | 17 |
-| 3 - 1 | 2 |
-| 9 - 8 | 1 |
-| 2 - 1 | 1 |
+#+BEGIN: problem-set :templates "add-sub-2" :count 12 :instruction "Compute
the answer"
+| problem | answer |
+|-----------+--------|
+| 2 + 6 | 8 |
+| 6 + 4 | 10 |
+| 9 + 5 | 14 |
+| 4 + 9 | 13 |
+| 6 - 4 | 2 |
+| 6 + 12 | 18 |
+| 5 + 3 + 1 | 9 |
+| 4 + 1 - 3 | 2 |
+| 2 + 7 - 5 | 4 |
+| 1 + 7 - 5 | 3 |
+| 9 + 6 - 7 | 8 |
+| 2 + 0 - 0 | 2 |
#+END:
* algebra
#+name: algebra-1
-| weight | order | template | descr |
-|--------+-------+------------------------------------+--------|
-| 3 | 1 | x / ([2..4] + [a=0..5]) = [$a..10] | simple |
-| 3 | 2 | [$a*[2..10]] / x = [a=1,2,4] | simple |
-| 1 | 3 | x^2 = sqrt([16 - $a] + [a=1..5]) | simple |
+| weight | order | template | descr
|
+|--------+-------+---------------------------------------+-----------------------|
+| 3 | 1 | x / ([2..4] + [a=0..5]) = [$a..10] | simple
|
+| 3 | 2 | [$a*[2..10]] / x = [a=1,2,4] | simple
|
+| 1 | 3 | x^2 = sqrt([16 - $a] + [a=1..5]) | sqrt
|
+| 3 | 4 | [-5..-1,1..5] x + [0..10] = [-10..10] | double range
sequence |
+| 3 | 4 | x = 2/x + sin([1..10]) | trig, two vars
|
#+BEGIN: problem-set :templates "algebra-1" :count 8 :instruction "Solve for x"
-| problem | answer |
-|--------------------+--------|
-| x / (3 + 0) = 5 | x = 15 |
-| x / (3 + 4) = 9 | x = 63 |
-| x / (3 + 0) = 4 | x = 12 |
-| 12 / x = 2 | x = 6 |
-| 8 / x = 2 | x = 4 |
-| 36 / x = 4 | x = 9 |
-| 6 / x = 2 | x = 3 |
-| x^2 = sqrt(13 + 3) | x = 2 |
+| problem | answer |
+|--------------------+-------------------|
+| x / (2 + 3) = 9 | x = 45 |
+| x / (3 + 3) = 9 | x = 54 |
+| 8 / x = 2 | x = 4 |
+| 36 / x = 4 | x = 9 |
+| x^2 = sqrt(12 + 4) | x = 2 |
+| -5 x + 8 = 0 | x = 8:5 |
+| -2 x + 3 = 0 | x = 3:2 |
+| x = 2/x + sin(2) | x = 1.43177096141 |
#+END:
diff --git a/mathsheet.org b/mathsheet.org
index 3f18868811..73279900a5 100644
--- a/mathsheet.org
+++ b/mathsheet.org
@@ -1,12 +1,87 @@
-* Goal
-The goal is to generate a math practice sheet made up of dynamic
-problems that are defined based on a set of flexible templates. The
-problem distribution and order should also be configurable.
-* Problem Templates
-** Overview
-This section contains some example templates. Each table defines a
-worksheet. Each time the worksheet is generated the problems are
-re-randomized.
+* TODO rename to org-mathsheet.el
+* Overview
+** Description
+This is a math worksheet generator. The worksheets are randomly
+generated based on templates that define what kinds of problems to
+include along with the order and relative frequency that each type of
+problem should appear on the worksheet.
+** Audience
+This could be useful for anyone that wants to provide math practice to
+someone else. It could be useful for a teacher, tutor, homeschool
+parent, or any parent.
+** Examples
+Here are some example worksheets generated by this tool:
+1. arithmatic
+2. algebra
+** Parts
+There are two main components involved in generating a worksheet:
+1. the problem templates
+2. the problem-set block
+*** Problem Templates
+**** Expression Templates
+The worksheet is made of a set of math problems. Each problem is
+defined by a template that lays out an equation or expression and
+shows where variables or numbers should be. For example, consider this
+template:
+#+begin_example
+[0..15] + [1..10]
+#+end_example
+The parts within the brackets are fields. When a template is made into
+a problem and added to a worksheet, each field is replaced by a number
+based on a set of rules. The supported rules are described in more
+detail below, but ~[0..15]~ means pick a random number between 0 and 15,
+inclusive, so the above template could result in problems like these:
+#+begin_example
+1 + 2
+15 + 10
+5 + 1
+#+end_example
+**** Equation Templates
+In additon to expressions where the answer is a number, templates can
+be equations where the solution is found by solving for the
+variable. For example, consider this template:
+#+begin_example
+[1..5] x + [0..10] = [-10..10]
+#+end_example
+This can produce the following problems:
+#+begin_example
+3 x + 6 = -1
+4 x + 2 = 2
+1 x + 8 = -3
+#+end_example
+**** Field Rules
+These are the field rules:
+- [-2..8] :: choose a random number from -2 to 8, inclusive
+- [1,3,5] :: choose randomly from 1, 3 or 5
+- [-3..-1,1..3] :: choose a random number from -3 to -1 or 1 to 3
+- [10/(2-1)] :: evaluate the expression
+- [a=...] :: assign the variable a to the number chosen for this field
+- [-2..$a] :: any number from -2 to the value assigned to ~a~ in another
+ field
+- [0..[$a/2]] :: any number from 0 to half the value assigned to ~a~.
+
+The ability to keep track of the random number chosen in one field and
+use it to influence another allows the template to be written to avoid
+answers that are negative or don't divide evenly.
+**** Template Examples
+Here are a few more examples:
+
+Division problem that divides evenly
+#+begin_example
+[$a*[1..5]] / [a=1..10]
+#+end_example
+
+Addition and subtraction, still with a positive result
+#+begin_example
+[a=1..10] + [b=0..10] - [0..($a+$b)]
+#+end_example
+
+*** The Problem Template Table
+
+You may want to have more than one type of problem on a worksheet, so
+
+Each table defines a worksheet. Each time the worksheet is generated
+the problems are re-randomized.
The table contains the following columns:
- weight :: the relative number of this type of problem to include on
@@ -15,19 +90,9 @@ The table contains the following columns:
problems with the same order will be intermingled.
- template :: this is the template used to generate problems of this
type. Templates are described in more detail below.
-- descr :: just notes, not used in worksheet generation.
+- descr :: just your notes, not used in worksheet generation.
-Templates are problems but the numbers are replaced with placeholders
-in square brackets.
-- [0..10] :: any number from 0 to 10
-- [a=...] :: assign the variable a to the number chosen for this field
-- [1,3,5] :: choose 1 or 3 or 5
-- [10/(2-1)] :: evaluate the expression
-- [-2..$a] :: any number from -2 to the value assigned to a in another
- placeholder
-- [0..[$a/2]] :: placeholders can be embedded within placeholders
-
-** Examples
+**** Examples
We label the table so that we can refer to it from the dynamic block
that generates the worksheet. Only the first three columns are used.
@@ -41,7 +106,9 @@ that generates the worksheet. Only the first three columns
are used.
| 1 | 3 | [1..10] + [1..7] + [1..5] | three terms
|
| 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three terms with
subtraction |
| 0 | 0 | [$a*[1..5]] / [a=1..10] | division
|
-
+*** Problem-Set Block
+**** Overview
+**** Examples
* Code walkthrough
** Problem generation
*** Header
@@ -57,17 +124,17 @@ This package needs
[[https://elpa.gnu.org/packages/peg.html][peg]].
#+end_src
*** Variables
-Need ~ianxm/var-list~ to keep track of the variables between fields.
+Need ~mathsheet--var-list~ to keep track of the variables between fields.
~worksheet-template~ is the LaTeX template for the worksheet.
#+name: variables
#+begin_src elisp :tangle mathsheet.el :var page=page
- (defvar ianxm/var-list '()
+ (defvar mathsheet--var-list '()
"List of variables used in a problem")
- (defconst ianxm/worksheet-template page
- "LaTeX template for worksheet")
+ (defconst mathsheet--worksheet-template page
+ "LaTeX template for the worksheet")
#+end_src
*** Scan problem
@@ -98,6 +165,7 @@ The last entry is ~nil~ for "not visited." It is used by
~dfs-visit~.
for example:
#+begin_example
[$a + 2 + [a=1..5]] => '((nil (a) m1 m19 nil) (a nil m11 m18 nil))
+ '((:fields (_0 (a a) (marker . marker) nil) (a nil
(marker . marker) nil)) (:alg-vars))
#+end_example
This uses the peg package to parse the problem. Instead of using the
@@ -110,8 +178,15 @@ new field to the list when we close the current field.
#+name: scan-problem
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/scan-problem ()
- "Scan problem"
+ (defun mathsheet--scan-problem ()
+ "Scan a problem.
+
+ This parses the problem and produces a list containing info about
+ its fields. For each field it returns a list containing:
+ 1. a symbol for the assigned variable or a unique placeholder
+ 2. a list of variables this field depends on
+ 3. a cons containing start and end markers for the field in the current
buffer
+ 4. `nil' which is used by `dfs-visit' later"
(let ((field-index 0)
open-fields ; stack
closed-fields ; list
@@ -122,7 +197,7 @@ new field to the list when we close the current field.
(field open (opt assignment) stuff close)
(space (* [space]))
(open (region "[")
- `(l r -- (progn
+ `(l _ -- (progn
(push (list
(intern (concat "_" (number-to-string
field-index))) ; asn-var
nil ; deps
@@ -131,28 +206,28 @@ new field to the list when we close the current field.
open-fields)
(setq field-index (1+ field-index))
".")))
- (assignment (region (substring letter)) "="
- `(l v r -- (progn
- (setcar
- (car open-fields)
- (intern v))
- ".")))
+ (assignment (substring letter) "="
+ `(v -- (progn
+ (setcar
+ (car open-fields)
+ (intern v))
+ ".")))
(asn-var "$" (substring letter)
- `(v -- (progn
- (push (intern v) (cadar open-fields))
- ".")))
+ `(v -- (progn
+ (push (intern v) (cadar open-fields))
+ ".")))
(alg-var (substring letter)
`(v -- (progn
(push v alg-vars)
".")))
(close (region "]")
- `(l r -- (progn
+ `(l _ -- (progn
(setcdr (caddar open-fields) (copy-marker l t))
(when (> (length open-fields) 1) ; add parent to
child dependency
(push (caar open-fields) (cadadr open-fields)))
(push (pop open-fields) closed-fields)
".")))
- (math-func (or "sqrt"))
+ (math-func (or "sqrt" "sin" "cos" "tan" "asin" "acos" "atan"
"floor" "ceil" "round"))
(letter [a-z])
(digit [0-9])
(symbol (or "." "," "+" "-" "*" "/" "^" "(" ")" "=")))
@@ -171,13 +246,13 @@ test scan
<<scan-problem>>
(with-temp-buffer
- (insert "y = [1..4] + [5,7,9]")
+ (insert "[0..4,6-9,11] * x + [floor([-10..10]/3)] = [-10..10]")
(goto-char (point-min))
- (ianxm/scan-problem))
+ (mathsheet--scan-problem))
#+end_src
#+RESULTS:
-: ((:fields (_1 nil (#<marker in no buffer> . #<marker (moves after insertion)
in no buffer>) nil) (_0 nil (#<marker in no buffer> . #<marker (moves after
insertion) in no buffer>) nil)) (:alg-vars "y"))
+: ((:fields (_3 nil (#<marker in no buffer> . #<marker (moves after insertion)
in no buffer>) nil) (_1 (_2) (#<marker in no buffer> . #<marker (moves after
insertion) in no buffer>) nil) (_2 nil (#<marker in no buffer> . #<marker
(moves after insertion) in no buffer>) nil) (_0 nil (#<marker in no buffer> .
#<marker (moves after insertion) in no buffer>) nil)) (:alg-vars "x"))
*** Reduce field
@@ -191,41 +266,58 @@ This uses the peg package to parse the field. This time
there
shouldn't be any fields embedded within the field. We should have
already evaluated and replaced them.
+We use ~..~ insead of ~-~ for range because if we used ~-~ then this would
+be ambiguous:
+#+begin_example
+[1-5]
+#+end_example
+
#+name: reduce-field
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/reduce-field ()
+ (defun mathsheet--reduce-field ()
+ "Reduce the field to a number.
+
+ Parse the field again, replacing spans with random numbers and
+ evaluating arithmetic operations. The field shouldn't have any
+ internal fields so this should result in a single number. Return
+ that number."
(with-peg-rules
- ((field "[" space (or range sequence assignment expression value)
space "]")
+ ((field "[" space (or math-func expression sequence assignment value)
space "]")
(expression (list value space operation space value (* space
operation space value))
`(vals -- (string-to-number
(calc-eval
- (mapconcat
- (lambda (x) (if (numberp x)
(number-to-string x) x))
- vals
- " ")))))
+ (list
+ (mapconcat
+ (lambda (x) (if (numberp x)
(number-to-string x) x))
+ vals
+ " "))
+ calc-prefer-frac nil))))
(operation (substring (or "+" "-" "*" "/")))
(assignment var-lhs space "=" space (or range sequence)
`(v r -- (progn
- (push (cons (intern v) r) ianxm/var-list)
+ (push (cons (intern v) r) mathsheet--var-list)
r)))
+ (sequence (list (or range value) (* "," space (or range value)))
+ `(vals -- (seq-random-elt vals)))
(range value ".." value
`(min max -- (+ (random (- max min)) min)))
- (sequence (list value "," value (* "," value))
- `(vals -- (seq-random-elt vals)))
(value (or (substring (opt "-") (+ digit)) var-rhs parenthetical)
`(v -- (if (stringp v) (string-to-number v) v)))
- (parenthetical "(" expression ")")
+ (parenthetical "(" (or expression value) ")")
(var-lhs (substring letter)) ; var for assignment
(var-rhs "$" (substring letter) ; var for use
- `(v -- (let ((val (alist-get (intern v) ianxm/var-list)))
+ `(v -- (let ((val (alist-get (intern v)
mathsheet--var-list)))
(or val (error "var %s not set" v)))))
+ (math-func (substring (or "sqrt" "sin" "cos" "tan" "asin" "acos"
"atan" "floor" "ceil" "round"))
+ parenthetical
+ `(f v -- (string-to-number (calc-eval (format "%s(%s)" f
v)))))
(space (* [space]))
(letter [a-z])
(digit [0-9]))
(peg-run (peg field)
(lambda (x) (message "failed %s" x))
- (lambda (x) (funcall x)))))
+ (lambda (x) (car (funcall x))))))
#+end_src
test with
@@ -235,13 +327,14 @@ test with
<<reduce-field>>
(with-temp-buffer
- (insert "[1..4]")
+ ;(insert "[1..10,15..20,50]")
+ (insert "[1..10]")
(goto-char (point-min))
- (ianxm/reduce-field))
+ (mathsheet--reduce-field))
#+end_src
#+RESULTS:
-: (1)
+: 8
*** Replace field
@@ -249,13 +342,18 @@ Replace a field with the value returned from reducing it.
#+name: replace-field
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/replace-field (node)
+ (defun mathsheet--replace-field (node)
+ "Replace a field with the number to which it reduces
+
+ Update the current buffer by replacing the field at point in the
+ current buffer with the number it reduces to. NODE contains the
+ info for the current field."
(let ((start (caaddr node))
(end (1+ (cdaddr node)))
val)
(goto-char start)
(when (looking-at "\\[")
- (setq val (car (ianxm/reduce-field)))
+ (setq val (mathsheet--reduce-field))
(goto-char start)
(delete-char (- end start) t)
(insert (number-to-string val)))))
@@ -269,7 +367,12 @@ the node.
#+name: dfs-visit
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/dfs-visit (node fields)
+ (defun mathsheet--dfs-visit (node fields)
+ "Visit NODE as part of a DFS of the problem
+
+ Traverse the fields of a problem using depth first search to
+ ensure that field replacement happens in dependency order. FIELDS
+ is a list of all fields in the problem."
(pcase (cadddr node)
(1 (error "cycle detected")) ; cycle
(2) ; skip
@@ -277,10 +380,10 @@ the node.
(setcar (cdddr node) 1) ; started
(let ((deps (cadr node)))
(dolist (dep deps)
- (ianxm/dfs-visit
+ (mathsheet--dfs-visit
(assq dep fields)
fields)))
- (ianxm/replace-field node) ; visit
+ (mathsheet--replace-field node) ; visit
(setcar (cdddr node) 2)))) ; mark done
#+end_src
@@ -293,21 +396,26 @@ processes all fields in a problem.
#+end_example
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/fill-problem (full-problem)
+ (defun mathsheet--fill-problem (full-problem)
+ "Replace all fields in FULL-PROBLEM
+
+ Goes through all fields in the given problem in dependency order
+ and replaces fields with numbers. When this completes the problem
+ will be ready to solve."
(with-temp-buffer
;; stage problem in temp buffer
(insert full-problem)
- (beginning-of-buffer)
+ (goto-char (point-min))
;; find fields, assignment variables, algebraic variables, dependencies
- (let* ((scan-ret (ianxm/scan-problem))
+ (let* ((scan-ret (mathsheet--scan-problem))
(fields (alist-get :fields scan-ret))
(alg-vars (alist-get :alg-vars scan-ret)))
;; visit fields ordered according to dependencies
(dolist (node fields)
- (ianxm/dfs-visit node fields))
- (setq ianxm/var-list '())
+ (mathsheet--dfs-visit node fields))
+ (setq mathsheet--var-list '())
;; return filled problem
`((:problem . ,(buffer-string))
@@ -322,11 +430,11 @@ test with this
<<replace-field>>
<<dfs-visit>>
- (ianxm/fill-problem "[1..12] + [1,4,6,10]")
- ;;(ianxm/fill-problem "[1..[2..[10..100]]]")
- ;;(ianxm/fill-problem "[$a*[1..10]] / [a=1..10]")
- ;;(ianxm/fill-problem "[$a]/(3+[a=1..5])")
- ;; (ianxm/fill-problem "1/x + 2 = [-10..[10..20]]")
+ (mathsheet--fill-problem "[1..12] + [1,4,6,10]")
+ ;;(mathsheet--fill-problem "[1..[2..[10..100]]]")
+ ;;(mathsheet--fill-problem "[$a*[1..10]] / [a=1..10]")
+ ;;(mathsheet--fill-problem "[$a]/(3+[a=1..5])")
+ ;; (mathsheet--fill-problem "1/x + 2 = [-10..[10..20]]")
#+end_src
@@ -366,7 +474,13 @@ other examples
#+name: generate-problems
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/generate-problems (template-name count)
+ (defun mathsheet--generate-problems (template-name count)
+ "Generate COUNT problems based on TEMPLATE-NAME
+
+ Generate problems and answers based on what is defined in the
+ given template table. The template table defines problem
+ templates as well as relative weights and how they should be
+ ordered."
(let (total-weight templates problems)
(save-excursion
(goto-char (point-min))
@@ -385,7 +499,7 @@ other examples
templates
0)))
;; calculate number for each row
- (dotimes (ii (length templates) problems)
+ (dotimes (ii (length templates))
(let* ((item (nth ii templates))
(weight (car item))
(needed (cond ; number of problems to add for this template
@@ -395,42 +509,39 @@ other examples
(- count (length problems)))
(t
(max (round (* (/ weight total-weight) count) ) 1))))
- problem answer)
-
- (let ((added 0)
- (dup-count 0)
- problem-set
- fill-ret problem solution)
- (while (< added needed) ; add until "needed" are kept
- (let* ((fill-ret (ianxm/fill-problem (caddr item)))
- (problem (alist-get :problem fill-ret))
- (alg-vars (alist-get :alg-vars fill-ret))
- (calc-string (if (not alg-vars)
- problem
- (format "solve(%s,[%s])" problem
(string-join alg-vars ","))))
- (solution
- (replace-regexp-in-string (rx (or "[" ".]" "]"))
- ""
- (calc-eval calc-string))))
- (cond
- ((member problem problem-set) ; dedup problems
- (setq dup-count (1+ dup-count))
- (when (> dup-count 100)
- ;; high number of dups indicates a narrow problem space
relative to problem count
- (error "Giving up, too many dups")))
- (t
- (push problem problem-set)
- (push (list problem ; problem
- solution ; solution
- (cadr item) ; order
- (not (null alg-vars))) ; true if algebraic
variables exist
- problems)
- (setq added (1+ added)))))))))
+ (added 0)
+ (dup-count 0)
+ problem-set)
+ (while (< added needed) ; add until "needed" are kept
+ (let* ((fill-ret (mathsheet--fill-problem (caddr item)))
+ (problem (alist-get :problem fill-ret))
+ (alg-vars (alist-get :alg-vars fill-ret))
+ (calc-string (if (not alg-vars)
+ problem
+ (format "solve(%s,[%s])" problem
(string-join (seq-uniq alg-vars) ","))))
+ (solution
+ (replace-regexp-in-string (rx (or "[" ".]" "]"))
+ ""
+ (calc-eval calc-string))))
+ (cond
+ ((member problem problem-set) ; dedup problems
+ (setq dup-count (1+ dup-count))
+ (when (> dup-count 100)
+ ;; high number of dups indicates a narrow problem space
relative to problem count
+ (error "Giving up, too many dups")))
+ (t
+ (push problem problem-set)
+ (push (list problem ; problem
+ solution ; solution
+ (cadr item) ; order
+ (not (null alg-vars))) ; true if algebraic
variables exist
+ problems)
+ (setq added (1+ added))))))))
;; shuffle
(dotimes (ii (- (length problems) 1))
(let ((jj (+ (random (- (length problems) ii)) ii)))
- (psetf (elt problems ii) (elt problems jj)
+ (cl-psetf (elt problems ii) (elt problems jj)
(elt problems jj) (elt problems ii))))
;; sort by order
@@ -450,24 +561,31 @@ I need to extract the values
- :templates :: templates
- :count :: 10
+- :instruction :: "Solve for x"
#+begin_src elisp :tangle mathsheet.el
(defun org-dblock-write:problem-set (params)
- "Update problem-set block and optionally write a worksheet."
+ "Update problem-set block and optionally write a worksheet.
+
+ PARAMS is a plist with the properties set on the dynamic block
+ header, which includes `:tempates' which is the name of the
+ templates table, `:count' which is the number of problems to put
+ on the worksheet, and `:instruction' which is the content of the
+ instruction line at the top of the page"
;; write the table header
(insert "| problem | answer |\n")
(insert "|-\n")
;; generate problem set
- (let ((problems (ianxm/generate-problems
+ (let ((problems (mathsheet--generate-problems
(plist-get params :templates)
(plist-get params :count))))
;; for each problem, write a row to the table
(insert
(mapconcat
- (lambda (problem) (format "|%s|%s|"
+ (lambda (problem) (format "| %s | %s |"
(car problem)
(cadr problem)))
problems
@@ -478,7 +596,7 @@ I need to extract the values
;; should we generate the sheet?
(when (y-or-n-p "Write worksheet? ")
- (ianxm/gen-worksheet
+ (mathsheet--gen-worksheet
(plist-get params :templates)
(plist-get params :instruction)
problems))))
@@ -549,7 +667,11 @@ Convert a calc expression to latex format.
#+name: convert-to-latex
#+begin_src elisp :tangle mathsheet.el
- (defun ianxm/convert-to-latex (expr)
+ (defun mathsheet--convert-to-latex (expr)
+ "Format the given calc expression EXPR for LaTeX
+
+ EXPR should be in normal calc format. The result is the same
+ expression (not simplified) but in LaTeX format."
(let* ((calc-language 'latex)
(calc-expr (math-read-expr expr))
(latex-expr (math-format-stack-value (list calc-expr 1 nil)))
@@ -565,9 +687,13 @@ template name will overwrite the same file.
#+begin_src elisp :results silent :tangle mathsheet.el
- (defun ianxm/gen-worksheet (template-name instruction problems)
- (with-temp-file (concat template-name ".tex")
- (insert ianxm/worksheet-template)
+ (defun mathsheet--gen-worksheet (file-name instruction problems)
+ "Generate a worksheet with PROBLEMS.
+
+ Write a file named FILE-NAME. Include the INSTRUCTION line at the
+ top."
+ (with-temp-file (concat file-name ".tex")
+ (insert mathsheet--worksheet-template)
(goto-char (point-min))
(search-forward "<<instruction>>")
@@ -580,16 +706,16 @@ template name will overwrite the same file.
(dolist (row problems)
(if (cadddr row)
(insert (format"\\CircledItem %s\\vspace{4cm}\n"
- (ianxm/convert-to-latex (car row))))
+ (mathsheet--convert-to-latex (car row))))
(insert (format"\\CircledItem %s =
\\rule[-.2\\baselineskip]{2cm}{0.4pt}\n"
- (ianxm/convert-to-latex (car row))))))
+ (mathsheet--convert-to-latex (car row))))))
(goto-char (point-min))
(search-forward "<<answers>>")
(replace-match "")
(dolist (row problems)
(insert (format "\\CircledItem %s\n"
- (ianxm/convert-to-latex (cadr row))))))
- (shell-command (concat "texi2pdf " template-name ".tex")
+ (mathsheet--convert-to-latex (cadr row))))))
+ (shell-command (concat "texi2pdf " file-name ".tex")
(get-buffer-create "*Standard output*")))
#+end_src