branch: externals/org-mathsheet
commit d7db7a61d13ee660bc342ce7be694dc9722497fd
Author: Ian Martins <ia...@jhu.edu>
Commit: Ian Martins <ia...@jhu.edu>

    Can produce algebra worksheets
---
 example.org   |  54 ++++++++------------
 mathsheet.org | 157 ++++++++++++++++++++++++++++++++++++----------------------
 2 files changed, 119 insertions(+), 92 deletions(-)

diff --git a/example.org b/example.org
index 3abcae6d71..aec3af0dbb 100644
--- a/example.org
+++ b/example.org
@@ -1,12 +1,12 @@
 * add and subtract
 
 #+name: add-sub-1
-| weight | order | template            | descr                                 
    |
-|--------+-------+---------------------+-------------------------------------------|
-|      3 |     1 | [1..10] + [0..10]   | simple                                
    |
-|      2 |     2 | [1..10] + [8..12]   | second number bigger                  
    |
-|      2 |     2 | [1..12] + [0..10]   | first number bigger                   
    |
-|      1 |     3 | [a=2..10] - [1..$a] | small number subtraction, positive 
result |
+| weight | order | template              | descr                               
      |
+|--------+-------+-----------------------+-------------------------------------------|
+|      3 |     1 | [1..10] + [0..10]     | simple                              
      |
+|      2 |     2 | [1..10] + [8..12]     | second number bigger                
      |
+|      2 |     2 | [1..12] + [0..10]     | first number bigger                 
      |
+|      1 |     3 | [a=2..10] - [1..$a]   | small number subtraction, positive 
result |
 
 #+name: add-sub-2
 | weight | order | template                      | descr                       
            |
@@ -19,34 +19,22 @@
 |      0 |     0 | [$a*[1..5]] / [a=1..10]       | division                    
            |
 
 #+name: algebra-1
-| weight | order | template                   | descr  |
-|--------+-------+----------------------------+--------|
-|      3 |     1 | x/2 + [a=0..20] = [$a..21] | 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 |
 
-#+BEGIN: problem-set :templates "algebra-1" :count 21
-| problem       | answer                |
-|---------------+-----------------------|
-| x/2 + 12 = 13 | x / 2. = 1            |
-| x/2 + 11 = 18 | x / 14. = 1           |
-| x/2 + 11 = 16 | x / 10. = 1           |
-| x/2 + 6 = 19  | x / 26. = 1           |
-| x/2 + 3 = 5   | x / 4. = 1            |
-| x/2 + 19 = 19 | x / 2 = 0             |
-| x/2 + 19 = 20 | x / 2. = 1            |
-| x/2 + 2 = 9   | x / 14. = 1           |
-| x/2 + 12 = 20 | x / 16. = 1           |
-| x/2 + 0 = 11  | x / 22. = 1           |
-| x/2 + 2 = 18  | x / 32. = 1           |
-| x/2 + 7 = 15  | x / 16. = 1           |
-| x/2 + 0 = 9   | x / 18. = 1           |
-| x/2 + 17 = 17 | x / 2 = 0             |
-| x/2 + 13 = 17 | x / 8. = 1            |
-| x/2 + 0 = 6   | x / 12. = 1           |
-| x/2 + 3 = 4   | x / 2. = 1            |
-| x/2 + 8 = 12  | x / 8. = 1            |
-| x/2 + 16 = 19 | x / 5.99999999999 = 1 |
-| x/2 + 7 = 13  | x / 12. = 1           |
-| x/2 + 9 = 20  | x / 22. = 1           |
+#+BEGIN: problem-set :templates "algebra-1" :count 8
+| problem         | answer |
+|-----------------+--------|
+| x / (2 + 2) = 2 | x = 8  |
+| x / (2 + 0) = 8 | x = 16 |
+| x / (3 + 1) = 1 | x = 4  |
+| x / (3 + 4) = 7 | x = 49 |
+| 2 / x = 1       | x = 2  |
+| 7 / x = 1       | x = 7  |
+| 10 / x = 2      | x = 5  |
+| 28 / x = 4      | x = 7  |
 #+END:
 
 * bigger addition and multiplications
diff --git a/mathsheet.org b/mathsheet.org
index 17b962d067..386147c5b2 100644
--- a/mathsheet.org
+++ b/mathsheet.org
@@ -44,12 +44,6 @@ that generates the worksheet. Only the first three columns 
are used.
 |      1 |     4 | [a=1..10] + [0..10] - [0..$a] | three terms with 
subtraction |
 |      0 |     0 | [$a*[1..5]] / [a=1..10]       | division                    
 |
 
-*** TODO handle templates with variables
-can be done like this
-#+begin_export
-(calc-eval "solve(2x = 1 + 2, [x])")
-#+end_export
-
 * Code walkthrough
 ** Problem generation
 *** Dependencies
@@ -81,13 +75,22 @@ end of the problem. returns a list of fields, formatted as:
 '(var (deps) start-marker end-marker nil)
 #+end_example
 
-~var~ is a variable name if there is an assignment, otherwise it is a
-placeholder like ~_0~, ~_1~, etc.
+change to
+
+#+begin_example
+'(asn-var (deps) (start-marker . end-marker) nil)
+#+end_example
+
+~asn-var~ is a variable name if there is an assignment, otherwise it is a
+placeholder like ~_0~, ~_1~, etc. ~asn-var~ must be interned and must
+be the first index since we use this list as an alist later.
+
+~alg-vars~ are algebraic variables if there are any in this problem,
+otherwise ~nil~.
 
 ~start-marker~ and ~end-marker~ are markers in the (temp) buffer.
 
-The last entry is ~nil~ for "not visited."  ~var~ must be interned and
-must be the first index since we use this as an alist later.
+The last entry is ~nil~ for "not visited." It is used by ~dfs-visit~.
 
 for example:
 #+begin_example
@@ -107,18 +110,21 @@ new field to the list when we close the current field.
   (defun ianxm/scan-problem ()
     "Scan problem"
     (let ((field-index 0)
-          open-fields ; stack (open close (vars) deps)
-          closed-fields) ; list (open close (vars) deps)
+          open-fields ; stack
+          closed-fields ; list
+          alg-vars)
 
       (with-peg-rules
-          ((stuff (* (or var letter digit symbol field space)))
+          ((stuff (* (or asn-var alg-var digit symbol field space)))
            (field open (opt assignment) stuff close)
            (space (* [space]))
            (open (region "[")
                  `(l r -- (progn
                             (push (list
-                                   (intern (concat "_" (number-to-string 
field-index)))
-                                   nil (copy-marker l) nil nil)
+                                   (intern (concat "_" (number-to-string 
field-index))) ; asn-var
+                                   nil ; deps
+                                   (cons (copy-marker l) nil) ; start and end 
markers
+                                   nil) ; not visited
                                   open-fields)
                             (setq field-index (1+ field-index))
                             ".")))
@@ -128,41 +134,46 @@ new field to the list when we close the current field.
                                      (car open-fields)
                                      (intern v))
                                     ".")))
-           (var "$" (substring letter)
+           (asn-var "$" (substring letter)
                 `(v -- (progn
                          (push (intern v) (cadar open-fields))
                          ".")))
+           (alg-var (substring letter)
+                    `(v -- (progn
+                             (push v alg-vars)
+                             ".")))
            (close (region "]")
                   `(l r -- (progn
-                             (setcar (cdddar open-fields) (copy-marker l t))
-                             (when (> (length open-fields) 1)
+                             (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)
                              ".")))
            (letter [a-z])
            (digit [0-9])
-           (symbol (or "." "+" "-" "*" "/" "(" ")" "=")))
+           (symbol (or "." "," "+" "-" "*" "/" "(" ")" "=")))
 
         (peg-run (peg stuff)
                  (lambda (x) (message "failed %s" x))
                  (lambda (x)
                    (funcall x)
-                   closed-fields)))))
+                   `((:fields . ,closed-fields)
+                     (:alg-vars . ,alg-vars)))))))
 #+end_src
 
 test scan
-#+begin_src elisp :noweb yes
+
+#+begin_src elisp :results verbatim :noweb yes
   <<scan-problem>>
 
   (with-temp-buffer
-    (insert "y = [1..4] + [5..9]")
+    (insert "y = [1..4] + [5,7,9]")
     (goto-char (point-min))
     (ianxm/scan-problem))
 #+end_src
 
 #+RESULTS:
-| _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 |
+: ((: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"))
 
 *** Reduce field
 
@@ -214,7 +225,8 @@ already evaluated and replaced them.
 #+end_src
 
 test with
-#+begin_src elisp :noweb yes :var page=page
+
+#+begin_src elisp :results verbatim :noweb yes :var page=page
   <<variables>>
   <<reduce-field>>
 
@@ -225,16 +237,17 @@ test with
 #+end_src
 
 #+RESULTS:
-| 3 |
+: (1)
 
 *** Replace field
 
 Replace a field with the value returned from reducing it.
 
+#+name: replace-field
 #+begin_src elisp :tangle mathsheet.el
   (defun ianxm/replace-field (node)
-    (let ((start (caddr node))
-          (end (1+ (cadddr node)))
+    (let ((start (caaddr node))
+          (end (1+ (cdaddr node)))
           val)
       (goto-char start)
       (when (looking-at "\\[")
@@ -250,21 +263,23 @@ This uses a depth first search to ensure that we visit 
(reduce and
 replace) the fields in dependency order. Check dependencies then visit
 the node.
 
+#+name: dfs-visit
 #+begin_src elisp :tangle mathsheet.el
   (defun ianxm/dfs-visit (node fields)
-    (pcase (nth 4 node)
+    (pcase (cadddr node)
       (1 (error "cycle detected")) ; cycle
       (2)                          ; skip
       (_                           ; process
-       (setcar (cddddr node) 1)  ; started
+       (setcar (cdddr node) 1)     ; started
        (let ((deps (cadr node)))
          (dolist (dep deps)
            (ianxm/dfs-visit
             (assq dep fields)
             fields)))
        (ianxm/replace-field node) ; visit
-       (setcar (cddddr node) 2)))) ; mark done
+       (setcar (cdddr node) 2)))) ; mark done
 #+end_src
+
 *** Fill fields in problem
 
 processes all fields in a problem.
@@ -275,37 +290,44 @@ processes all fields in a problem.
 
 #+begin_src elisp :tangle mathsheet.el
   (defun ianxm/fill-problem (full-problem)
-      (interactive)
-      (let (fields)
-        (with-temp-buffer
-          ;; stage problem in temp buffer
-          (insert full-problem)
-          (beginning-of-buffer)
+      (with-temp-buffer
+        ;; stage problem in temp buffer
+        (insert full-problem)
+        (beginning-of-buffer)
 
-          ;; find fields, assignments, dependencies
-          (setq fields (ianxm/scan-problem))
+        ;; find fields, assignment variables, algebraic variables, dependencies
+        (let* ((scan-ret (ianxm/scan-problem))
+               (fields (alist-get :fields scan-ret))
+               (alg-vars (alist-get :alg-vars scan-ret)))
 
-          ;; order fields according to dependencies
+          ;; visit fields ordered according to dependencies
           (dolist (node fields)
             (ianxm/dfs-visit node fields))
           (setq ianxm/var-list '())
-          (buffer-string))))
+
+          ;; return filled problem
+          `((:problem . ,(buffer-string))
+            (:alg-vars . ,alg-vars)))))
 #+end_src
 
 test with this
-#+begin_src elisp :noweb yes
-  <<full>>
+#+begin_src elisp :results verbatim :noweb yes :var page=page
+  <<variables>>
+  <<scan-problem>>
+  <<reduce-field>>
+  <<replace-field>>
+  <<dfs-visit>>
 
-  ;;(ianxm/fill-problem "[1..12] + [1..10]")
+  (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 "[-10..[10..20]]")
+  ;; (ianxm/fill-problem "1/x + 2 = [-10..[10..20]]")
 
 #+end_src
 
 #+RESULTS:
-: -7
+: ((:problem . "6 + [1,4,6,10]") (:alg-vars))
 
 other examples
 #+begin_example
@@ -374,16 +396,32 @@ other examples
           (let ((added 0)
                 (dup-count 0)
                 problem-set
-                problem)
+                fill-ret problem solution)
             (while (< added needed) ; add until "needed" are kept
-              (setq problem (ianxm/fill-problem (caddr item)))
-              (if (member problem problem-set) ; dedup problems
+              (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))
-                (push problem problem-set)
-                (push (list problem (calc-eval problem) (cadr item)) problems)
-                (setq added (1+ added)))
-              (when (> dup-count 100)
-                (error "Giving up, too many dups"))))))
+                  (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))
@@ -395,9 +433,7 @@ other examples
       (sort problems (lambda (a b) (< (caddr a) (caddr b))))
 
       ;; return problems and answers, drop header
-      (mapcar
-       (lambda (x) (seq-take x 2))
-       problems)))
+      problems))
 #+end_src
 
 ** Update problem-set block
@@ -517,8 +553,11 @@ same file.
       (search-forward "<<problems>>")
       (replace-match "")
       (dolist (row problems)
-        (insert (format"\\CircledItem %s = 
\\rule[-.2\\baselineskip]{2cm}{0.4pt}\n\n"
-                       (car row))))
+        (if (cadddr row)
+            (insert (format"\\CircledItem %s\\vspace{4cm}\n"
+                       (car row)))
+          (insert (format"\\CircledItem %s = 
\\rule[-.2\\baselineskip]{2cm}{0.4pt}\n"
+                         (car row)))))
       (goto-char (point-min))
       (search-forward "<<answers>>")
       (replace-match "")

Reply via email to