Browse Source

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.
Nicolas Goaziou 5 years ago
parent
commit
c158bf2f16
2 changed files with 68 additions and 54 deletions
  1. 37 54
      lisp/org-colview.el
  2. 31 0
      testing/lisp/test-org-colview.el

+ 37 - 54
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)
 

+ 31 - 0
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)))))