Skip to content

Commit

Permalink
added hashtable based grouping back in
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbysmith007 committed May 19, 2011
1 parent 721ddae commit da0b1c0
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 150 deletions.
2 changes: 1 addition & 1 deletion README.mediawiki
Expand Up @@ -30,7 +30,7 @@ of grouped-list objects
''tests'': a list of tests to compare the keys with<br /> ''tests'': a list of tests to compare the keys with<br />


''grouping-implmentation'': What data structure should be used to perform the grouping<br /> ''grouping-implmentation'': What data structure should be used to perform the grouping<br />
'':alist, :tree , :hash-table''<br /> '':list, :hash-table''<br />
The implementation doesnt change the output, but it does change The implementation doesnt change the output, but it does change
the performance characteristics of the grouped-object (see: the performance characteristics of the grouped-object (see:
grouped-list-speed-tester for help deciding which to use) grouped-list-speed-tester for help deciding which to use)
Expand Down
182 changes: 86 additions & 96 deletions examples/examples.lisp
Expand Up @@ -51,41 +51,41 @@


(defparameter +example-timeclock-objs+ (defparameter +example-timeclock-objs+
(iter top (for i from 0 to 10) (iter top (for i from 0 to 10)
(iter (for rec in +example-timeclock-data+) (iter (for rec in +example-timeclock-data+)
(in top (collect (apply #'example-tcr rec)))))) (in top (collect (apply #'example-tcr rec))))))


(defun timeclock-report-rec-print (gl &optional (spaces "")) (defun timeclock-report-rec-print (gl &optional (spaces ""))
(let ((further-groups (child-groupings gl))) (let ((further-groups (child-groupings gl)))
(if further-groups (if further-groups
(iter (iter
(with hours = 0) (with hours = 0)
(for group in further-groups) (for group in further-groups)
(format T "~?~A~%" spaces () (key-value group) ) (format T "~?~A~%" spaces () (key-value group) )
(incf hours (timeclock-report-rec-print (incf hours (timeclock-report-rec-print
group (concatenate 'string spaces "~2,1@T"))) group (concatenate 'string spaces "~2,1@T")))
(finally (finally
(format T "~?Total: ~D~%" spaces () hours ) (format T "~?Total: ~D~%" spaces () hours )
(return hours))) (return hours)))
(iter (for kid in (items-in-group gl)) (iter (for kid in (items-in-group gl))
(sum (hours kid) into hours) (sum (hours kid) into hours)
(finally (finally
(format T "~?Total: ~D~%" spaces () hours ) (format T "~?Total: ~D~%" spaces () hours )
(return hours)))))) (return hours))))))


(defun print-timeclock-report () (defun print-timeclock-report ()
(let ((by-person-project (let ((by-person-project
(make-grouped-list (make-grouped-list
+example-timeclock-objs+ +example-timeclock-objs+
:keys (list #'name #'proj) :keys (list #'name #'proj)
:tests (list #'string-equal #'eql) :tests (list #'equalp #'eql)
:grouping-implementation :hash-table)) :grouping-implementation :hash-table))
(by-project-person (by-project-person
(make-grouped-list (make-grouped-list
+example-timeclock-objs+ +example-timeclock-objs+
:keys (list #'proj #'name) :keys (list #'proj #'name)
:tests (list #'eql #'string-equal) :tests (list #'eql #'equalp)
:grouping-implementation :tree))) :grouping-implementation :list)))

(format T "Hours BY Project > Person~%-----------~%") (format T "Hours BY Project > Person~%-----------~%")
(timeclock-report-rec-print by-project-person) (timeclock-report-rec-print by-project-person)
(format T "~%~%Hours BY Person > Project~%-----------~%") (format T "~%~%Hours BY Person > Project~%-----------~%")
Expand Down Expand Up @@ -137,10 +137,11 @@ bob
Total: 165 Total: 165
|# |#



(defparameter +example-speedtest-timeclock-objs+ (defparameter +example-speedtest-timeclock-objs+
(iter top (for i from 0 to 100) (iter top (for i from 0 to 100)
(iter (for rec in +example-timeclock-data+) (iter (for rec in +example-timeclock-data+)
(in top (collect (apply #'example-tcr rec)))))) (in top (collect (apply #'example-tcr rec))))))


(defun speed-test-example () (defun speed-test-example ()
(format *trace-output* "~%build-gl-speed-test~%") (format *trace-output* "~%build-gl-speed-test~%")
Expand All @@ -149,7 +150,7 @@ Total: 165
:list +example-speedtest-timeclock-objs+ :list +example-speedtest-timeclock-objs+
:keys (list #'name #'proj) :keys (list #'name #'proj)
:tests (list #'string-equal #'eql)) :tests (list #'string-equal #'eql))

(format *trace-output* "~%~%build-gl-speed-test with-item-access (format *trace-output* "~%~%build-gl-speed-test with-item-access
This shows how implementations differ based on workload~%") This shows how implementations differ based on workload~%")
(grouped-list-speed-tester (grouped-list-speed-tester
Expand All @@ -158,41 +159,37 @@ This shows how implementations differ based on workload~%")
:keys (list #'name #'proj) :keys (list #'name #'proj)
:tests (list #'string-equal #'eql) :tests (list #'string-equal #'eql)
:actions (lambda (gl) :actions (lambda (gl)
(iter (for c from 0 to 1000 ) (iter (for c from 0 to 1000 )
(iter (iter
(for i in '("russ" "bob")) (for i in '("russ" "bob"))
(items-in-group gl i) (items-in-group gl i)
(iter (for j in `(:proj-a :proj-b :proj-c)) (iter (for j in `(:proj-a :proj-b :proj-c))
(items-in-group gl i j))))))) (items-in-group gl i j)))))))


#| #|
build-gl-speed-test build-gl-speed-test
Grouping Implentation Speed Tests Grouping Implentation Speed Tests
HASH-TABLE Implementation HASH-TABLE Implementation
Evaluation took: Evaluation took:
0.519 seconds of real time 0.181 seconds of real time
0.490000 seconds of total run time (0.420000 user, 0.070000 system) 0.160000 seconds of total run time (0.120000 user, 0.040000 system)
[ Run times consist of 0.270 seconds GC time, and 0.220 seconds non-GC time. ] [ Run times consist of 0.080 seconds GC time, and 0.080 seconds non-GC time. ]
94.41% CPU 88.40% CPU
1,294,672,493 processor cycles 92 lambdas converted
99,023,280 bytes consed 451,161,165 processor cycles
4,939,600 bytes consed
TREE Implementation
Evaluation took:
0.091 seconds of real time
0.070000 seconds of total run time (0.070000 user, 0.000000 system)
76.92% CPU
225,769,988 processor cycles
1,540,032 bytes consed
ALIST Implementation
LIST Implementation
Evaluation took: Evaluation took:
0.095 seconds of real time 0.107 seconds of real time
0.100000 seconds of total run time (0.090000 user, 0.010000 system) 0.100000 seconds of total run time (0.080000 user, 0.020000 system)
105.26% CPU 93.46% CPU
236,701,268 processor cycles 46 lambdas converted
1,507,136 bytes consed 265,815,870 processor cycles
2,749,600 bytes consed
Expand All @@ -202,30 +199,23 @@ Grouping Implentation Speed Tests
HASH-TABLE Implementation HASH-TABLE Implementation
Evaluation took: Evaluation took:
0.668 seconds of real time 0.873 seconds of real time
0.620000 seconds of total run time (0.600000 user, 0.020000 system) 0.860000 seconds of total run time (0.760000 user, 0.100000 system)
[ Run times consist of 0.260 seconds GC time, and 0.360 seconds non-GC time. ] [ Run times consist of 0.330 seconds GC time, and 0.530 seconds non-GC time. ]
92.81% CPU 98.51% CPU
1,664,663,812 processor cycles 2,175,013,267 processor cycles
157,757,008 bytes consed 294,658,896 bytes consed
TREE Implementation
Evaluation took:
1.647 seconds of real time
1.590000 seconds of total run time (1.360000 user, 0.230000 system)
[ Run times consist of 0.720 seconds GC time, and 0.870 seconds non-GC time. ]
96.54% CPU
4,107,706,057 processor cycles
439,151,536 bytes consed
ALIST Implementation
LIST Implementation
Evaluation took: Evaluation took:
0.548 seconds of real time 1.031 seconds of real time
0.510000 seconds of total run time (0.480000 user, 0.030000 system) 0.990000 seconds of total run time (0.900000 user, 0.090000 system)
[ Run times consist of 0.230 seconds GC time, and 0.280 seconds non-GC time. ] [ Run times consist of 0.460 seconds GC time, and 0.530 seconds non-GC time. ]
93.07% CPU 96.02% CPU
1,364,044,695 processor cycles 2,572,489,710 processor cycles
147,965,152 bytes consed 293,604,720 bytes consed
|# |#


(defparameter +example-names+ #("russ" "alice" "bob" "charlie")) (defparameter +example-names+ #("russ" "alice" "bob" "charlie"))
Expand All @@ -235,18 +225,18 @@ Evaluation took:
"An example of building a grouped list up from individual items "An example of building a grouped list up from individual items
rather than starting with a full list then grouping it" rather than starting with a full list then grouping it"
(iter (for type in `(:hash-table :tree :alist)) (iter (for type in `(:hash-table :tree :alist))
(iter (iter
(with gl = (make-grouped-list (with gl = (make-grouped-list
nil nil
:keys (list #'name #'proj) :keys (list #'name #'proj)
:tests (list #'string-equal #'eql) :tests (list #'string-equal #'eql)
:grouping-implementation type)) :grouping-implementation type))
(for i from 0 to 1000) (for i from 0 to 1000)
(for tcr = (for tcr =
(example-tcr (example-tcr
(alexandria:random-elt +example-names+) (alexandria:random-elt +example-names+)
(random 10) (random 10)
(alexandria:random-elt +example-projects+))) (alexandria:random-elt +example-projects+)))
(add-item-to-grouping tcr gl) (add-item-to-grouping tcr gl)
(finally (timeclock-report-rec-print gl) (finally (timeclock-report-rec-print gl)
(format T "----------------------~%"))))) (format T "----------------------~%")))))
44 changes: 25 additions & 19 deletions group-by.lisp
Expand Up @@ -83,26 +83,30 @@ eg: (group-by '((a 1 2) (a 3 4) (b 5 6)))
(defclass grouped-list () (defclass grouped-list ()
((orig-list :accessor orig-list :initarg :orig-list :initform nil) ((orig-list :accessor orig-list :initarg :orig-list :initform nil)
(grouping-implementation (grouping-implementation
:accessor grouping-implementation :initarg :grouping-implementation :initform :alist :accessor grouping-implementation :initarg :grouping-implementation :initform :list
:documentation :documentation
"What data structure should be used to perform the grouping "What data structure should be used to perform the grouping :list, :hash-table")
:alist, :tree , :hash-table")
(keys :accessor keys :initarg :keys :initform nil (keys :accessor keys :initarg :keys :initform nil
:documentation "A list of key functions we will use to group the list") :documentation "A list of key functions we will use to group the list")
(tests :accessor tests :initarg :tests :initform nil (tests :accessor tests :initarg :tests :initform nil
:documentation "A list of test functions we will use to test key equality :documentation "A list of test functions we will use to test key equality
tree: defaults to #'equal tree: defaults to #'equal
hash-table: this be a single hash-equality symbol (defaults to 'equal)") hash-table: this be a single hash-equality symbol (defaults to 'equal)")
(child-groupings :accessor child-groupings :initarg :child-groupings :initform nil) (%child-groupings :accessor %child-groupings :initarg :%child-groupings :initform nil)
(%items :accessor %items :initarg :%items :initform nil) (%items :accessor %items :initarg :%items :initform nil)
(child-map :accessor child-map :initarg :child-map :initform nil)
(parent-grouping :accessor parent-grouping :initarg :parent :initform nil (parent-grouping :accessor parent-grouping :initarg :parent :initform nil
:documentation "If this is a subgrouping of another grouped-list, what is the parent grouping we are apart of (mostly for testing)") :documentation "If this is a subgrouping of another grouped-list, what is the parent grouping we are apart of (mostly for testing)")
(key-value :accessor key-value :initarg :key-value :initform nil (key-value :accessor key-value :initarg :key-value :initform nil
:documentation "If this is a subgrouping of another grouped-list, what is the key this grouped-list represents in the parent grouping (mostly for testing)")) :documentation "If this is a subgrouping of another grouped-list, what is the key this grouped-list represents in the parent grouping (mostly for testing)"))
(:documentation "This class represents a list that we have grouped by multiple key values (:documentation "This class represents a list that we have grouped by multiple key values
ala one of the group-by-repeatedly functions ")) ala one of the group-by-repeatedly functions "))


(defmethod child-groupings ((gl grouped-list))
(case (grouping-implementation gl)
(:hash-table (iter (for (k v) in-hashtable (%child-groupings gl))
(collect v)))
(T (%child-groupings gl))))

(defun make-grouped-list (inp &key tests keys (grouping-implementation :alist)) (defun make-grouped-list (inp &key tests keys (grouping-implementation :alist))
"Given a list of input, produce a grouped-list CLOS object that contains "Given a list of input, produce a grouped-list CLOS object that contains
the original list, configuration about the groupings and the result tree the original list, configuration about the groupings and the result tree
Expand All @@ -126,13 +130,19 @@ of grouped-list objects
(defmethod initialize-instance :after ((o grouped-list) &key list &allow-other-keys) (defmethod initialize-instance :after ((o grouped-list) &key list &allow-other-keys)
(unless (listp (keys o)) (setf (keys o) (list (keys o)))) (unless (listp (keys o)) (setf (keys o) (list (keys o))))
(unless (listp (tests o)) (setf (tests o) (list (tests o)))) (unless (listp (tests o)) (setf (tests o) (list (tests o))))
(when (eql :hash-table (grouping-implementation o))
(setf (%child-groupings o)
(make-hash-table :test (or (first (tests o)) 'equal))))

(when list ;; only do this if we are not a child-grouped-list (when list ;; only do this if we are not a child-grouped-list
(setf (orig-list o) list) (setf (orig-list o) list)
(iter (for x in list) (iter (for x in list)
(add-item-to-grouping x o)))) (add-item-to-grouping x o))))


(defun find-single-sub-category (gl key-value &key test) (defun find-single-sub-category (gl key-value &key test)
(find key-value (child-groupings gl) :key #'key-value :test test)) (case (grouping-implementation gl)
(:hash-table (gethash key-value (%child-groupings gl)))
(t (find key-value (%child-groupings gl) :key #'key-value :test test))))


(defmethod categorize-item (item (root grouped-list) &key &allow-other-keys) (defmethod categorize-item (item (root grouped-list) &key &allow-other-keys)
(iter (iter
Expand All @@ -152,7 +162,7 @@ of grouped-list objects


(defmethod add-item-to-grouping (item (gl grouped-list)) (defmethod add-item-to-grouping (item (gl grouped-list))
"puts a new item in the grouping of the grouped list (but not in the original list)" "puts a new item in the grouping of the grouped list (but not in the original list)"
(categorize-item item gl :keys (keys gl) :tests (tests gl))) (categorize-item item gl))


(defmethod %grouping-items ((gl grouped-list)) (defmethod %grouping-items ((gl grouped-list))
"Returns the items in a given group" "Returns the items in a given group"
Expand All @@ -170,7 +180,10 @@ of grouped-list objects
:grouping-implementation (grouping-implementation gl) :grouping-implementation (grouping-implementation gl)
:parent-grouping gl :parent-grouping gl
:key-value key-value))) :key-value key-value)))
(push c (child-groupings gl)) (case (grouping-implementation gl)
(:hash-table
(setf (gethash key-value (%child-groupings gl)) c))
(t (push c (%child-groupings gl))))
c)) c))


(defmethod items-in-group ((gl grouped-list) &rest key-values) (defmethod items-in-group ((gl grouped-list) &rest key-values)
Expand All @@ -181,7 +194,7 @@ of grouped-list objects
(for key in key-values) (for key in key-values)
(for test = (or (first tests) #'equal)) (for test = (or (first tests) #'equal))
(setf tests (rest tests)) (setf tests (rest tests))
(setf subgroup (find-single-sub-category gl key :test test))) (setf subgroup (find-single-sub-category subgroup key :test test)))


;; Get all the items for that subgrouping (for alists this is a list we just produced) ;; Get all the items for that subgrouping (for alists this is a list we just produced)
;; and that list will simply pass through ;; and that list will simply pass through
Expand All @@ -202,17 +215,10 @@ of grouped-list objects
:list list :keys keys :tests hash-tests :list list :keys keys :tests hash-tests
:grouping-implementation :hash-table))) :grouping-implementation :hash-table)))
(when actions (funcall actions gl))))) (when actions (funcall actions gl)))))
(format *trace-output* "~%~%TREE Implementation~%" ) (format *trace-output* "~%~%LIST Implementation~%" )
(time
(iter (for i from 1 to iterations)
(let ((gl (make-instance 'grouped-list :list list :keys keys :tests tests
:grouping-implementation :tree)))
(when actions (funcall actions gl)))
))
(format *trace-output* "~%~%ALIST Implementation~%" )
(time (time
(iter (for i from 1 to iterations) (iter (for i from 1 to iterations)
(let ((gl (make-instance 'grouped-list :list list :keys keys :tests tests (let ((gl (make-instance 'grouped-list :list list :keys keys :tests tests
:grouping-implementation :alist))) :grouping-implementation :list)))
(when actions (funcall actions gl))) (when actions (funcall actions gl)))
))) )))

0 comments on commit da0b1c0

Please sign in to comment.