From c158bf2f1679b1a3d6ab777784ee69e339f382d6 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Wed, 17 Feb 2016 21:35:34 +0100 Subject: [PATCH] org-colview: Refactor low-high estimates * lisp/org-colview.el (org-columns-string-to-number): (org-columns-number-to-string): Handle estimates. (org-columns-estimate-combine): Rename this to... (org-columns--estimate-combine): ... this. (org-columns-compile-map): (org-columns-compute): Apply renaming. (org-estimate-mean-and-var): (org-estimate-print): (org-string-to-estimate): Remove functions. * testing/lisp/test-org-colview.el (test-org-colview/columns-summary): Add tests. --- lisp/org-colview.el | 91 +++++++++++++------------------- testing/lisp/test-org-colview.el | 31 +++++++++++ 2 files changed, 68 insertions(+), 54 deletions(-) diff --git a/lisp/org-colview.el b/lisp/org-colview.el index 40422184a..c5bf0113b 100644 --- a/lisp/org-colview.el +++ b/lisp/org-colview.el @@ -770,7 +770,7 @@ When COLUMNS-FMT-STRING is non-nil, use it as the column format." ("@min" min_age min) ("@max" max_age max) ("@mean" mean_age (lambda (&rest x) (/ (apply '+ x) (float (length x))))) - ("est+" estimate org-estimate-combine)) + ("est+" estimate org-columns--estimate-combine)) "Operator <-> format,function map. Used to compile/uncompile columns format and completing read in interactive function `org-columns-new'. @@ -970,19 +970,21 @@ display, or in the #+COLUMNS line of the current buffer." level last-level) level (org-outline-level) val (org-entry-get nil property) - valflag (and val (string-match "\\S-" val))) + valflag (org-string-nw-p val)) (cond ((< level last-level) ;; Put the sum of lower levels here as a property. If - ;; values are estimate, use an appropriate sum function. - (setq sum (funcall - (if (eq fun 'org-estimate-combine) #'org-estimate-combine - #'+) - (if (and (/= last-level inminlevel) - (aref lvals last-level)) - (apply fun (aref lvals last-level)) 0) - (if (aref lvals inminlevel) - (apply fun (aref lvals inminlevel)) 0)) + ;; values are estimates, use an appropriate sum function. + (setq sum (funcall (if (eq fun 'org-columns--estimate-combine) + #'org-columns--estimate-combine + #'+) + (if (and (/= last-level inminlevel) + (aref lvals last-level)) + (apply fun (aref lvals last-level)) + 0) + (if (aref lvals inminlevel) + (apply fun (aref lvals inminlevel)) + 0)) flag (or (aref lflag last-level) ; any valid entries from children? (aref lflag inminlevel)) ; or inline tasks? str (org-columns-number-to-string sum format printf) @@ -1037,7 +1039,9 @@ display, or in the #+COLUMNS line of the current buffer." FMT is a symbol describing the summary type. Optional argument PRINTF, when non-nil, is a format string used to print N." (cond - ((eq fmt 'estimate) (org-estimate-print n printf)) + ((eq fmt 'estimate) + (let ((fmt (or printf "%.0f"))) + (mapconcat (lambda (n) (format fmt n)) (if (consp n) n (list n n)) "-"))) ((not (numberp n)) "") ((memq fmt '(add_times max_times min_times mean_times)) (org-hours-to-clocksum-string n)) @@ -1057,6 +1061,22 @@ PRINTF, when non-nil, is a format string used to print N." (format-seconds "%dd %.2hh %mm %ss" n)) (t (number-to-string n)))) +(defun org-columns--estimate-combine (&rest estimates) + "Combine a list of estimates, using mean and variance. +The mean and variance of the result will be the sum of the means +and variances (respectively) of the individual estimates." + (let ((mean 0) + (var 0)) + (dolist (e estimates) + (pcase e + (`(,low ,high) + (let ((m (/ (+ low high) 2.0))) + (cl-incf mean m) + (cl-incf var (- (/ (+ (* low low) (* high high)) 2.0) (* m m))))) + (value (cl-incf mean value)))) + (let ((sd (sqrt var))) + (list (- mean sd) (+ mean sd))))) + (defun org-columns-string-to-number (s fmt) "Convert a column value S to a number. FMT is a symbol describing the summary type." @@ -1081,7 +1101,11 @@ FMT is a symbol describing the summary type." (setq sum (+ (string-to-number n) (/ sum 60)))))) ((memq fmt '(checkbox checkbox-n-of-m checkbox-percent)) (if (equal s "[X]") 1. 0.000001)) - ((eq fmt 'estimate) (org-string-to-estimate s)) + ((eq fmt 'estimate) + (if (not (string-match "\\(.*\\)-\\(.*\\)" s)) + (string-to-number s) + (list (string-to-number (match-string 1 s)) + (string-to-number (match-string 2 s))))) ((string-match-p org-columns--fractional-duration-re s) (let ((s (concat "0:" (org-duration-string-to-minutes s t))) (sum 0.0)) @@ -1495,47 +1519,6 @@ This will add overlays to the date lines, to show the summary for each day." (equal (nth 4 a) (nth 4 fm))) (org-columns-compute (car fm))))))))))) -(defun org-estimate-mean-and-var (v) - "Return the mean and variance of an estimate." - (let* ((v (cond ((consp v) v) - ((numberp v) (list v v)) - (t (error "Invalid estimate type")))) - (low (float (car v))) - (high (float (cadr v))) - (mean (/ (+ low high) 2.0)) - (var (/ (+ (expt (- mean low) 2.0) (expt (- high mean) 2.0)) 2.0))) - (list mean var))) - -(defun org-estimate-combine (&rest el) - "Combine a list of estimates, using mean and variance. -The mean and variance of the result will be the sum of the means -and variances (respectively) of the individual estimates." - (let ((mean 0) - (var 0)) - (mapc (lambda (e) - (let ((stats (org-estimate-mean-and-var e))) - (setq mean (+ mean (car stats))) - (setq var (+ var (cadr stats))))) - el) - (let ((stdev (sqrt var))) - (list (- mean stdev) (+ mean stdev))))) - -(defun org-estimate-print (e &optional fmt) - "Prepare a string representation of an estimate. -This formats these numbers as two numbers with a \"-\" between them." - (let ((fmt (or fmt "%.0f")) - (e (cond ((consp e) e) - ((numberp e) (list e e)) - (t (error "Invalid estimate type"))))) - (format "%s" (mapconcat (lambda (n) (format fmt n)) e "-")))) - -(defun org-string-to-estimate (s) - "Convert a string to an estimate. -The string should be two numbers joined with a \"-\"." - (if (string-match "\\(.*\\)-\\(.*\\)" s) - (list (string-to-number (match-string 1 s)) - (string-to-number(match-string 2 s))) - (list (string-to-number s) (string-to-number s)))) (provide 'org-colview) diff --git a/testing/lisp/test-org-colview.el b/testing/lisp/test-org-colview.el index 75c3e2ea8..642d372f8 100644 --- a/testing/lisp/test-org-colview.el +++ b/testing/lisp/test-org-colview.el @@ -452,6 +452,37 @@ :A: 5d 3h :END:" (let ((org-columns-default-format "%A{@min}")) (org-columns)) + (get-char-property (point) 'org-columns-value-modified)))) + ;; {est+} gives a low-high estimate using mean and standard + ;; deviation. + (should + (equal + "3-17" + (org-test-with-temp-text + "* H +** S1 +:PROPERTIES: +:A: 0-10 +:END: +** S1 +:PROPERTIES: +:A: 0-10 +:END:" + (let ((org-columns-default-format "%A{est+}")) (org-columns)) + (get-char-property (point) 'org-columns-value-modified)))) + ;; When using {est+} summary, a single number is understood as + ;; a degenerate range. + (should + (equal + "4-4" + (org-test-with-temp-text + "* H +** S1 +:PROPERTIES: +:A: 4 +:END: +" + (let ((org-columns-default-format "%A{est+}")) (org-columns)) (get-char-property (point) 'org-columns-value-modified)))))