branch: externals/org-mathsheet commit 1cd7959d035fff1dc262f44e7ae0932c7642c57d Author: Ian Martins <ia...@jhu.edu> Commit: Ian Martins <ia...@jhu.edu>
Can generate sheet based on templates --- mathsheet.org | 202 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 83 deletions(-) diff --git a/mathsheet.org b/mathsheet.org index e3a13d57ae..2f08aec235 100644 --- a/mathsheet.org +++ b/mathsheet.org @@ -4,7 +4,7 @@ the goal is to generate a math practice sheet. similar to https://www.math-aids.com * script ** vars --#+property: header-args+ :var student="Noble" problem-count=24 +#+property: header-args+ :var student="Noble" problem-count=26 ** problem sets *** add and subtract @@ -14,9 +14,9 @@ similar to https://www.math-aids.com |--------+-------+-------------------------------+------------------------| | 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=3..10] - [0..$a] | subtraction | -| 1 | 4 | [a=1..10] + [0..10] - [1..$a] | three with subtraction | +| 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three with subtraction | | 0 | 0 | [$a*[1..5]] / [a=1..10] | division | ** problem generation @@ -159,23 +159,6 @@ reduces. (lambda (x) (funcall x))))) #+end_src -#+begin_example - simple range - [10..11] - - complex range - [-10..[10..20]] - - complex with assignment - [a=1..[2..8]] - - complex with inner assignment - [-10..[b=10..20]] - - simple with variable - [0..[$a..$b]] -#+end_example - *** replace field replace a field with the value returned from processing it. @@ -234,7 +217,7 @@ processes all fields in a problem. ;; find fields, assignments, dependencies (setq fields (ianxm/scan-problem)) - (message "fields %s" fields) + ;;(message "fields %s" fields) ;; order fields according to dependencies (dolist (node fields) @@ -247,93 +230,146 @@ test with this #+begin_src elisp :noweb yes <<full>> - (ianxm/fill-problem "[1..12] + [1..10]") + ;;(ianxm/fill-problem "[1..12] + [1..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]]") #+end_src #+RESULTS: -: 11 + 8 +: -7 + +other examples +#+begin_example + simple range + [10..11] + + complex range + [-10..[10..20]] + + complex with assignment + [a=1..[2..8]] + + complex with inner assignment + [-10..[b=10..20]] + + simple with variable + [0..[$a..$b]] +#+end_example *** full script tangles everything needed to convert a template to a problem #+name: full #+begin_src elisp :noweb yes :tangle mathsheet.el -<<var-list>> + <<var-list>> -<<scan-problem>> + <<scan-problem>> -<<reduce-field>> + <<reduce-field>> -<<catalog-fields>> + <<catalog-fields>> -<<replace-field>> + <<replace-field>> -<<dfs-visit>> + <<dfs-visit>> -<<fill-problem>> -#+end_src -** generate problem set from template + <<fill-problem>> -#+name: problem-set -#+begin_src elisp :noweb yes :var template=first-set - (let (prob ret) - (dotimes (index problem-count ret) - (setq prob - <<one-problem>>) - (setq ret (push (list (car prob) (cdr prob)) ret )))) + <<generate-problems>> +#+end_src +** generate problem set from templates + +1. load table +2. determine how many of each + 1. sort by weight, low to high + 2. for each row + 1. calculate number, round with min 1, but 0->0 + 3. for last entry (highest weight) just take however many are left. + 4. produce '(order template nil) for each problem + 5. convert to '(order problem answer) +3. sort +4. loop through list, replacing entry with '(problem . solution) + +#+name: generate-problems +#+begin_src elisp :results table :var templates=firstset + (defun ianxm/generate-problems () + (let (total-weight problems) + ;; sort by weight (low to high) + (setq templates (sort templates (lambda (a b) (< (car a) (car b)))) + ;; calc total weight + total-weight (float + (seq-reduce (lambda (total item) (+ total (car item))) + templates + 0))) + ;; calculate number for each row + (dotimes (ii (length templates) problems) + (let* (problem answer + (item (nth ii templates)) + (weight (car item)) + (needed (cond + ((= weight 0) + 0) + ((= ii (1- (length templates))) + (- problem-count (length problems))) + (t + (max (round (* (/ weight total-weight) problem-count) ) 1))))) + ;; add problems to list + (dotimes (jj needed) + (let* ((problem (ianxm/fill-problem (nth 2 item))) + (answer (calc-eval problem)) + (order (nth 1 item))) + (setq problems (push (list order problem answer) problems)))))) + + ;; shuffle + (dotimes (ii (- (length problems) 1)) + (let ((jj (+ (random (- (length problems) ii)) ii))) + (psetf (elt problems ii) (elt problems jj) + (elt problems jj) (elt problems ii)))) + + ;; sort by order + (sort problems (lambda (a b) (< (car a) (car b)))) + + ;; remove the "order" column and return + (mapcar (lambda (x) (seq-drop x 1)) problems))) #+end_src +test with this -#+name: one-problem -#+begin_src elisp :var template=first-set - (let (vars probs prob weight order answ) - ;; parse input - (dolist (line (split-string template "\n")) - (pcase (read (concat "(" line ")")) - (`(var ,name in [,min ,max]) - ;; assign vars - (push (cons name (+ (random (- max min -1)) min)) vars)) - ((and - (pred (lambda (x) (eq 'problem (car x)))) - `(problem weight ,weight order ,order ,prob)) - ;; save problems - (push (cons prob weight) - probs)))) - ;; choose problem given weights - (let ((tot (reduce - (lambda (tot prob) (+ tot (cdr prob))) - probs - :initial-value 0)) - indx chosen-prob) - (setq indx (random tot) - chosen-prob (reduce - (lambda (rem prob) - (cond - ((not (numberp rem)) rem) - ((> rem 0) (setq rem (- rem (cdr prob))) (if (> rem 0) rem (car prob))) - (t (car prob)))) - probs - :initial-value indx)) - ;; do replacements - (dolist (var vars) - (setq chosen-prob (replace-regexp-in-string - (symbol-name (car var)) - (number-to-string (cdr var)) - chosen-prob))) - ;; calculate answer - (setq answ (calc-eval chosen-prob)) - - (cons chosen-prob answ))) -#+end_src +#+name: problem-set +#+begin_src elisp :results table :noweb yes :var templates=firstset + <<full>> -#+RESULTS: one-problem -: (7 + 8 . 15) + (ianxm/generate-problems) +#+end_src -*** TODO dedup +#+RESULTS: problem-set +| 9 + 9 | 18 | +| 1 + 4 | 5 | +| 8 + 2 | 10 | +| 3 + 7 | 10 | +| 6 + 4 | 10 | +| 1 + 1 | 2 | +| 7 + 4 | 11 | +| 5 + 5 | 10 | +| 4 + 1 | 5 | +| 9 + 13 | 22 | +| 2 + 14 | 16 | +| 4 + 10 | 14 | +| 9 + 11 | 20 | +| 4 + 12 | 16 | +| 3 + 12 | 15 | +| 3 + 4 + 3 | 10 | +| 2 + 6 + 1 | 9 | +| 7 + 5 + 1 | 13 | +| 8 - 7 | 1 | +| 8 + 1 - 3 | 6 | +| 4 - 0 | 4 | +| 6 + 3 - 3 | 6 | +| 3 - 0 | 3 | +| 8 + 7 - 5 | 10 | ** lay out problems and answers this generates a problem set.