Author: sburson Date: Sat Nov 12 21:21:18 2011 New Revision: 27
Log: Merging lots of stuff in from development branch for 1.3.0 release.
Highlights:
* Added `:default' feature to `map' constructor macro.
* Some new modify macros: `includef' (replaces `adjoinf'), `excludef' (replaces `removef'), `intersectf', `imagef', `composef'. `reduce' now works on maps.
* Added operations:
() `split' (two-valued `filter') () `splice' (splice a seq into another seq) () `appendf' and `prependf' (seq concat-and-assign) () `tuple-key-name' (new export)
Also, changed `concat' from binary to n-ary.
* Made the methods for `sort' and `stable-sort' on CL sequences copy the sequence first, so these are now functional operations -- consistent with FSet semantics, but not with their CL definitions. (In practice the sequence usually has to be copied anyway; and you can always call `cl:sort' explicitly if you don't want it to be copied.)
* Made (convert 'vector seq) always return a simple-vector, instead of figuring out dynamically whether to return a string (which fails on the empty seq, duh). Added (convert 'string seq) that always returns a string (errors if it can't).
* New, experimental type `list-relation'. Various other improvements to relations.
Modified: trunk/Code/defs.lisp trunk/Code/fset.lisp trunk/Code/order.lisp trunk/Code/reader.lisp trunk/Code/relations.lisp trunk/Code/testing.lisp trunk/Code/tuples.lisp trunk/Code/wb-trees.lisp
Modified: trunk/Code/defs.lisp ============================================================================== --- trunk/Code/defs.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/defs.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -37,6 +37,7 @@ ;; are unlikely to be useful in user code. #:equal? #:compare #:compare-slots #:identity-ordering-mixin #:define-cross-type-compare-methods + #:compare-lexicographically #:empty? nonempty? #:size #:set-size #:arb #:contains? #:domain-contains? #:range-contains? #:member? #:multiplicity #:empty-set #:empty-bag #:empty-map #:empty-seq #:empty-tuple @@ -47,14 +48,15 @@ #:union #:bag-sum #:intersection #:bag-product #:complement #:set-difference #:set-difference-2 #:bag-difference #:subset? #:disjoint? #:subbag? - #:filter #:image #:reduce #:domain #:range #:with-default + #:filter #:filter-pairs #:split + #:image #:reduce #:domain #:range #:with-default #:map-union #:map-intersection #:map-difference-2 #:restrict #:restrict-not #:compose #:map-default #:first #:last #:lastcons #:head #:tail #:with-first #:less-first #:push-first #:pop-first - #:with-last #:less-last #:push-last #:pop-last - #:insert #:subseq #:concat #:reverse #:sort #:stable-sort + #:with-last #:less-last #:push-last #:pop-last #:appendf #:prependf + #:insert #:splice #:subseq #:concat #:reverse #:sort #:stable-sort #:find #:find-if #:find-if-not #:count #:count-if #:count-if-not #:position #:position-if #:position-if-not @@ -62,8 +64,10 @@ #:substitute #:substitute-if #:substitute-if-not #:convert #:iterator #:do-set #:do-bag #:do-bag-pairs #:do-map #:do-seq #:do-tuple - #:adjoinf #:removef #:unionf - #:def-tuple-key #:get-tuple-key #:tuple-merge + #:adjoinf #:removef #:includef #:excludef + #:unionf #:intersectf #:imagef #:composef + #:define-tuple-key #:def-tuple-key #:get-tuple-key #:tuple-key-name + #:tuple-merge #:fset-setup-readtable #:*fset-readtable* #:$ ;; Used by the bag methods that convert to and from lists. @@ -72,7 +76,12 @@ #:bounded-set #:make-bounded-set #:bounded-set-contents ;; Relations #:relation #:bin-rel #:wb-bin-rel #:empty-bin-rel #:empty-wb-bin-rel - #:lookup-inv #:inverse #:join #:conflicts)) + #:lookup-inv #:inverse #:join #:conflicts #:map-to-sets + #:list-relation #:wb-list-relation #:empty-list-relation + #:empty-wb-list-relation #:arity #:query #:query-multi #:do-list-relation + #:query-registry #:empty-query-registry #:with-query #:less-query + #:all-queries #:lookup-multi #:forward-key #:lookup-restricted + #:lookup-multi-restricted))
;;; A convenient package for experimenting with FSet. Also serves as an example
Modified: trunk/Code/fset.lisp ============================================================================== --- trunk/Code/fset.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/fset.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -234,6 +234,20 @@ as a Lisp function, `fn' can be a map, or a set (which is treated as mapping its members to true and everything else to false)."))
+(defgeneric split (fn collection) + (:documentation + "Returns two values, (filter fn collection) and +(filter (cl:complement fn) collection).")) + +(defgeneric filter-pairs (fn collection) + (:documentation + "Just like `filter' except that if invoked on a bag, `fn' (which must be a +Lisp function) is called with two arguments for each pair, the member and the +multiplicity.")) + +(defmethod filter-pairs (fn (collection t)) + (filter fn collection)) + (defgeneric image (fn collection) (:documentation "Returns a new collection containing the result of applying `fn' to each @@ -243,7 +257,20 @@ map, or a set (which is treated as mapping its members to true and everything else to false). `collection' can also be a map, in which case `fn' must be a Lisp function of two arguments that returns two values (the map-default of the -result is that of `collection').")) +result is that of `collection'); also see `compose'.")) + +;;; Convenience methods. +(defmethod image ((fn function) (l list)) + (mapcar fn l)) + +(defmethod image ((fn symbol) (l list)) + (mapcar (coerce fn 'function) l)) + +(defmethod image ((fn map) (l list)) + (mapcar (lambda (x) (@ fn x)) l)) + +(defmethod image ((fn set) (l list)) + (mapcar (lambda (x) (@ fn x)) l))
(defgeneric reduce (fn collection &key key initial-value) (:documentation @@ -261,6 +288,8 @@ (:documentation "Returns the domain of the map, that is, the set of keys mapped by the map."))
+;;; &&& Actually I think this should return a bag. You can then convert it +;;; to a set if you want. (defgeneric range (map) (:documentation "Returns the range of the map, that is, the set of all values to which keys @@ -303,6 +332,23 @@ but not `map2', with the same default as `map1'; and one containing all the pairs that are in `map2' but not `map1', with the same default as `map2'."))
+;;; Possible operation: `map-update' (better name??), which would be like +;;; `map-union' except the keys would be exactly the keys of `map1'. This +;;; would be useful for removing items from chained maps: +;;; +;;; (map-update chained-map +;;; (map (key1 (map (key2 (set val))))) +;;; (fn (x y) (map-update x y #'set-difference))) +;;; +;;; If key1->key2->val is not already present, this returns `chained-map'. +;;; +;;; But another operation with a legitimate claim on the name would simply +;;; apply a function to the range value for a specified key: +;;; +;;; (map-update chained-map key1 +;;; (fn (m) (map-update m key2 +;;; (fn (s) (less s val))))) + (defgeneric restrict (map set) (:documentation "Returns a map containing only those pairs of `map' whose keys are @@ -318,7 +364,8 @@ (:documentation "Returns a new map with the same domain as `map1', which maps each member of that domain to the result of applying first `map1' to it, then applying -`map2-or-fn' to the result.")) +`map2-or-fn' to the result. `map2-or-fn' can also be a sequence, which is +treated as a map from indices to members."))
(defgeneric first (seq) (:documentation @@ -369,11 +416,17 @@ is extended in either direction if needed prior to the insertion; previously uninitialized indices are filled with the seq's default)."))
+(defgeneric splice (seq idx subseq) + (:documentation + "Returns a new sequence like `seq' but with the elements of `subseq' inserted +at `idx' (the seq is extended in either direction if needed prior to the insertion; +previously uninitialized indices are filled with the seq's default).")) + ;;; &&& Maybe we should shadow `concatenate' instead, so you can specify a ;;; result type. -(defgeneric concat (seq1 seq2) +(defgeneric concat (seq1 &rest seqs) (:documentation - "Returns the concatenation of `seq1' and `seq2'.")) + "Returns the concatenation of `seq1' with each of `seqs'."))
;;; This is the opposite order from `cl:coerce', but I like it better, because I @@ -508,19 +561,21 @@ "Returns `seq' sorted by `pred', a function of two arguments; if `key' is supplied, it is a function of one argument that is applied to the elements of `seq' before they are passed to `pred'. The sort is not guaranteed to be -stable.")) +stable. The method for CL sequences copies the sequence first, unlike +`cl:sort'."))
(defmethod sort ((s sequence) pred &key key) - (cl:sort s pred :key key)) + (cl:sort (cl:copy-seq s) pred :key key))
(defgeneric stable-sort (seq pred &key key) (:documentation "Returns `seq' sorted by `pred', a function of two arguments; if `key' is supplied, it is a function of one argument that is applied to the elements of -`seq' before they are passed to `pred'. The sort is guaranteed to be stable.")) +`seq' before they are passed to `pred'. The sort is guaranteed to be stable. +The method for CL sequences copies the sequence first, unlike `cl:stable-sort'."))
(defmethod stable-sort ((s sequence) pred &key key) - (cl:stable-sort s pred :key key)) + (cl:stable-sort (cl:copy-seq s) pred :key key))
(defgeneric find (item collection &key key test) (:documentation @@ -820,6 +875,8 @@ `(lookup ,access-form ,key-temp))))
+;;; `adjoinf' / `removef', which don't form a good pair, are now deprecated +;;; in favor of `includef' / `excludef'. (define-modify-macro adjoinf (&rest item-or-tuple) with "(adjoinf coll . args) --> (setf coll (with coll . args))") @@ -828,9 +885,29 @@ less "(removef coll . args) --> (setf coll (less coll . args))")
+(define-modify-macro includef (&rest item-or-tuple) + with + "(includef coll . args) --> (setf coll (with coll . args))") + +(define-modify-macro excludef (&rest item-or-tuple) + less + "(excludef coll . args) --> (setf coll (less coll . args))") + (define-modify-macro unionf (set) union)
+(define-modify-macro intersectf (set) + intersection) + +(define-modify-macro imagef (fn) + ximage) + +(defun ximage (coll fn) + (image fn coll)) + +(define-modify-macro composef (fn) + compose) + (define-modify-macro push-first (val) with-first "(push-first seq val) --> (setf seq (with-first seq val))") @@ -863,6 +940,15 @@ (setq ,(car new) (less-last ,(car new))) ,setter))))
+(define-modify-macro appendf (seq) + concat) + +(define-modify-macro prependf (seq) + xconcat) + +(defun xconcat (seq1 seq2) + (concat seq2 seq1)) +
;;; ================================================================================ ;;; Sets @@ -1120,14 +1206,6 @@ (incf i)) result))
-(defmethod convert ((to-type (eql 'seq)) (s set) &key) - ;; Not sure we can improve on this much. - (convert 'seq (convert 'list s))) - -(defmethod convert ((to-type (eql 'wb-seq)) (s set) &key) - ;; Not sure we can improve on this much. - (convert 'wb-seq (convert 'list s))) - (defmethod convert ((to-type (eql 'set)) (l list) &key) (make-wb-set (WB-Set-Tree-From-List l)))
@@ -1223,20 +1301,13 @@ (count-if #'(lambda (x) (not (funcall pred x))) s :key key)))
(defun print-wb-set (set stream level) - (if (and *print-level* (>= level *print-level*)) - (format stream "#") - (progn - (format stream "#{") - (let ((i 0)) - (do-set (x set) - (format stream " ") - (when (and *print-length* (>= i *print-length*)) - (format stream "...") - (return)) - (incf i) - (let ((*print-level* (and *print-level* (1- *print-level*)))) - (write x :stream stream)))) - (format stream " }")))) + (declare (ignore level)) + (pprint-logical-block (stream nil :prefix "#{" :suffix " }") + (do-set (x set) + (pprint-pop) + (write-char #\Space stream) + (pprint-newline :linear stream) + (write x :stream stream))))
(def-gmap-arg-type :set (set) "Yields the elements of `set'." @@ -1261,6 +1332,15 @@ `(nil #'WB-Set-Tree-With #'make-wb-set ,filterp))
+(def-gmap-res-type :union (&key filterp) + "Returns the union of the values, optionally filtered by `filterp'." + `((set) #'union nil ,filterp)) + +(def-gmap-res-type :intersection (&key filterp) + "Returns the intersection of the values, optionally filtered by `filterp'." + `((complement (set)) #'intersection nil ,filterp)) + + ;;; ================================================================================ ;;; Bags
@@ -1381,11 +1461,11 @@ (make-wb-bag (WB-Bag-Tree-Intersect (wb-bag-contents s1) (wb-bag-contents s2))))
(defmethod intersection ((s wb-set) (b wb-bag) &key) - (make-wb-bag (WB-Set-Tree-Intersect (wb-set-contents s) + (make-wb-set (WB-Set-Tree-Intersect (wb-set-contents s) (WB-Bag-Tree-To-Set-Tree (wb-bag-contents b)))))
(defmethod intersection ((b wb-bag) (s wb-set) &key) - (make-wb-bag (WB-Set-Tree-Intersect (WB-Bag-Tree-To-Set-Tree (wb-bag-contents b)) + (make-wb-set (WB-Set-Tree-Intersect (WB-Bag-Tree-To-Set-Tree (wb-bag-contents b)) (wb-set-contents s))))
(defmethod bag-product ((b1 wb-bag) (b2 wb-bag)) @@ -1487,6 +1567,19 @@ (defmethod filter ((pred bag) (b bag)) (bag-filter pred b))
+(defun bag-filter-pairs (pred b) + (let ((result nil)) + (do-bag-pairs (x n b) + (when (funcall pred x n) + (setq result (WB-Bag-Tree-With result x n)))) + (make-wb-bag result))) + +(defmethod filter-pairs ((pred function) (b bag)) + (bag-filter-pairs pred b)) + +(defmethod filter-pairs ((pred symbol) (b bag)) + (bag-filter-pairs (coerce pred 'function) b)) + (defmethod image ((fn function) (b bag)) (bag-image fn b))
@@ -1690,28 +1783,18 @@ (count-if #'(lambda (x) (not (funcall pred x))) s :key key)))
(defun print-wb-bag (bag stream level) - (if (and *print-level* (>= level *print-level*)) - (format stream "#") - (progn - (format stream "#{% ") - (let ((i 0)) - (do-bag-pairs (x n bag) - (when (> i 0) - (format stream " ")) - (when (and *print-length* (>= i *print-length*)) - (format stream "...") - (return)) - (incf i) - (let ((*print-level* (and *print-level* (1- *print-level*)))) - (if (> n 1) - (progn - (format stream "#%") - (write `(,x ,n) :stream stream)) - (write x :stream stream)))) - (when (> i 0) - (format stream " "))) - (format stream "%}")))) - + (declare (ignore level)) + (pprint-logical-block (stream nil :prefix "#{%" :suffix " %}") + (let ((i 0)) + (do-bag-pairs (x n bag) + (pprint-pop) + (write-char #\Space stream) + (pprint-newline :linear stream) + (incf i) + (if (> n 1) + (progn + (write `(,x ,n) :stream stream)) + (write x :stream stream))))))
(def-gmap-arg-type :bag (bag) "Yields each element of `bag', as many times as its multiplicity." @@ -1904,6 +1987,27 @@ (setq result (WB-Map-Tree-With result new-x new-y)))) (make-wb-map result (map-default m))))
+(defmethod reduce ((fn function) (m map) &key key (initial-value nil init?)) + (map-reduce fn m initial-value (and key (coerce key 'function)) init?)) + +(defmethod reduce ((fn symbol) (m map) &key key (initial-value nil init?)) + (map-reduce (coerce fn 'function) m initial-value (and key (coerce key 'function)) + init?)) + +(defun map-reduce (fn m initial-value key init?) + (declare (optimize (speed 3) (safety 0)) + (type function fn) + (type (or function null) key)) + (unless init? + (error 'simple-program-error + :format-control "~A on a map requires an initial value" + :format-arguments '(reduce))) + (let ((result initial-value)) + (do-map (x y m) + (let ((x y (if key (funcall key x y) (values x y)))) + (setq result (funcall fn result x y)))) + result)) + (defmethod range ((m map)) (let ((s nil)) (do-map (key val m) @@ -1966,6 +2070,9 @@ (defmethod compose ((m wb-map) (fn symbol)) (map-fn-compose m (coerce fn 'function)))
+(defmethod compose ((m wb-map) (s seq)) + (map-fn-compose m (fn (x) (@ s x)))) + (defun map-fn-compose (m fn) (make-wb-map (WB-Map-Tree-Compose (wb-map-contents m) fn) (funcall fn (map-default m)))) @@ -2116,27 +2223,14 @@ (count-if #'(lambda (x) (not (funcall pred x))) m :key key)))
(defun print-wb-map (map stream level) - (if (and *print-level* (>= level *print-level*)) - (format stream "#") - (progn - (format stream "#{| ") - (let ((i 0)) - (do-map (x y map) - (when (> i 0) - (format stream " ")) - (when (and *print-length* (>= i *print-length*)) - (format stream "...") - (return)) - (incf i) - (let ((*print-level* (and *print-level* (1- *print-level*)))) - (write (list x y) :stream stream :pretty nil))) - (when (> i 0) - (format stream " "))) - (format stream "|}") - (let ((default (map-default map))) - (when default - (format stream "/~A" default)))))) - + (declare (ignore level)) + (pprint-logical-block (stream nil :prefix "#{|") + (do-map (x y map) + (pprint-pop) + (write-char #\Space stream) + (pprint-newline :linear stream) + (write (list x y) :stream stream)) + (format stream " |}~:[~;/~:*~A~]" (map-default map))))
(def-gmap-arg-type :map (map) "Yields each pair of `map', as two values." @@ -2274,6 +2368,26 @@ (make-wb-seq (WB-Seq-Tree-Insert tree idx val) (seq-default s))))
+(defmethod splice ((s wb-seq) idx subseq) + (let ((tree (wb-seq-contents s)) + ((size (WB-Seq-Tree-Size tree))) + (subseq-tree (wb-seq-contents (convert 'wb-seq subseq)))) + (when (< idx 0) + (setq tree (WB-Seq-Tree-Concat + (WB-Seq-Tree-From-Vector + (make-array (- idx) :initial-element (seq-default s))) + tree)) + (setq idx 0)) + (when (> idx size) + (setq tree (WB-Seq-Tree-Concat + tree (WB-Seq-Tree-From-Vector + (make-array (- idx size) :initial-element (seq-default s))))) + (setq size idx)) + (make-wb-seq (WB-Seq-Tree-Concat (WB-Seq-Tree-Concat (WB-Seq-Tree-Subseq tree 0 idx) + subseq-tree) + (WB-Seq-Tree-Subseq tree idx (WB-Seq-Tree-Size tree))) + (seq-default s)))) + (defmethod less ((s wb-seq) idx &optional (arg2 nil arg2?)) (declare (ignore arg2)) (check-two-arguments arg2? 'less 'wb-seq) @@ -2283,10 +2397,11 @@ (make-wb-seq (WB-Seq-Tree-Remove tree idx) (seq-default s)) s)))
-(defmethod concat ((s1 wb-seq) (s2 wb-seq)) - (make-wb-seq (WB-Seq-Tree-Concat (wb-seq-contents s1) (wb-seq-contents s2)) - ;; Don't see what to do but pick one arbitrarily. - (seq-default s1))) +(defmethod concat ((s1 seq) &rest seqs) + (let ((tree (wb-seq-contents s1))) + (dolist (seq seqs) + (setq tree (WB-Seq-Tree-Concat tree (wb-seq-contents (convert 'seq seq))))) + (make-wb-seq tree (seq-default s1))))
(defmethod subseq ((s wb-seq) start &optional end) (let ((tree (wb-seq-contents s)) @@ -2332,6 +2447,10 @@ (defmethod convert ((to-type (eql 'vector)) (s wb-seq) &key) (WB-Seq-Tree-To-Vector (wb-seq-contents s)))
+;;; Always returns a string. Signals `type-error' if it encounters a non-character. +(defmethod convert ((to-type (eql 'string)) (s wb-seq) &key) + (WB-Seq-Tree-To-String (wb-seq-contents s))) + (defmethod convert ((to-type (eql 'seq)) (l list) &key) (make-wb-seq (WB-Seq-Tree-From-List l)))
@@ -2341,6 +2460,10 @@ (defmethod convert ((to-type (eql 'list)) (s wb-seq) &key) (WB-Seq-Tree-To-List (wb-seq-contents s)))
+(defmethod convert ((to-type (eql 'seq)) (s set) &key) + ;; Not sure we can improve on this much. + (convert 'seq (convert 'list s))) + (defmethod convert ((to-type (eql 'wb-seq)) (s set) &key) ;; Not sure we can improve on this much. (convert 'wb-seq (convert 'list s))) @@ -2448,6 +2571,35 @@ (make-wb-seq (WB-Seq-Tree-From-List (nreverse result)) (seq-default s))))
+(defmethod split ((fn function) (s seq)) + (seq-split fn s)) + +(defmethod split ((fn symbol) (s seq)) + (seq-split (coerce fn 'function) s)) + +(defmethod split ((fn map) (s seq)) + (seq-split #'(lambda (x) (lookup fn x)) s)) + +(defmethod split ((fn set) (s seq)) + (seq-split #'(lambda (x) (lookup fn x)) s)) + +(defmethod split ((fn bag) (s seq)) + (seq-split #'(lambda (x) (lookup fn x)) s)) + +(defun seq-split (fn s) + (declare (optimize (speed 3) (safety 0)) + (type function fn)) + (let ((result-1 nil) + (result-2 nil)) + (do-seq (x s) + (if (funcall fn x) + (push x result-1) + (push x result-2))) + (values (make-wb-seq (WB-Seq-Tree-From-List (nreverse result-1)) + (seq-default s)) + (make-wb-seq (WB-Seq-Tree-From-List (nreverse result-2)) + (seq-default s))))) + (defmethod image ((fn function) (s seq)) (seq-image fn s))
@@ -2750,26 +2902,14 @@ :key key :start start :end end :from-end from-end :count count)))
(defun print-wb-seq (seq stream level) - (if (and *print-level* (>= level *print-level*)) - (format stream "#") - (progn - (format stream "#[ ") - (let ((i 0)) - (do-seq (x seq) - (when (> i 0) - (format stream " ")) - (when (and *print-length* (>= i *print-length*)) - (format stream "...") - (return)) - (incf i) - (let ((*print-level* (and *print-level* (1- *print-level*)))) - (write x :stream stream))) - (when (> i 0) - (format stream " "))) - (format stream "]") - (let ((default (seq-default seq))) - (when default - (format stream "/~A" default)))))) + (declare (ignore level)) + (pprint-logical-block (stream nil :prefix "#[") + (do-seq (x seq) + (pprint-pop) + (write-char #\Space stream) + (pprint-newline :linear stream) + (write x :stream stream)) + (format stream " ]~:[~;/~:*~A~]" (seq-default seq))))
(def-gmap-arg-type :seq (seq) "Yields the elements of `seq'." @@ -2797,6 +2937,10 @@ #'(lambda (s) (convert 'seq (nreverse s))) ,filterp))
+(def-gmap-res-type :concat (&key filterp) + "Returns the concatenation of the seq values, optionally filtered by `filterp'." + `((seq) #'concat nil ,filterp)) +
;;; ================================================================================ ;;; CL Sequences @@ -2812,6 +2956,14 @@ (defmethod size ((s sequence)) (length s))
-(defmethod lookup ((s sequence) idx) +(defmethod lookup ((s sequence) (idx integer)) (elt s idx))
+ +;;; ================================================================================ +;;; Miscellany + +;;; Oooops -- I somehow thought CL already had this. +(define-condition simple-program-error (simple-condition program-error) + ()) +
Modified: trunk/Code/order.lisp ============================================================================== --- trunk/Code/order.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/order.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -33,7 +33,17 @@ ;;; Makes it easy to define `compare' methods on new classes. Just say: ;;; ;;; (defmethod compare ((f1 frob) (f2 frob)) -;;; (compare-slots f1 f2 #'frob-foo #'frob-bar)) +;;; (compare-slots f1 f2 'foo #'frob-bar)) +;;; +;;; where `foo' is a slot and `frob-bar' is an accessor (or any other +;;; function on your class). +;;; +;;; If you want distinct instances to never compare `:equal', put `:eql' +;;; at the end of the accessor list to specify that `eql' is the final +;;; determiner of equality for your type: +;;; +;;; (defmethod compare ((f1 frob) (f2 frob)) +;;; (compare-slots f1 f2 'foo #'frob-bar :eql)) ;;; (defmacro compare-slots (obj1 obj2 &rest accessors) "A handy macro for writing the bodies of `compare' methods for user classes. @@ -44,13 +54,23 @@ example, if class `frob' has accessor `frob-foo' and slot `bar':
(defmethod compare ((f1 frob) (f2 frob)) - (compare-slots f1 f2 #'frob-foo 'bar))" + (compare-slots f1 f2 #'frob-foo 'bar)) + +If the symbol `:eql' is supplied as the last accessor, then if the comparisons +by the other supplied accessors all return `:equal' but `obj1' and `obj2' are +not eql, this returns `:unequal'." (let ((default-var (gensym "DEFAULT-")) (comp-var (gensym "COMP-")) (obj1-var (gensym "OBJ1-")) (obj2-var (gensym "OBJ2-"))) (labels ((rec (accs) - (if (null accs) default-var + (if (or (null accs) + (and (eq (car accs) ':eql) + (or (null (cdr accs)) + (error "If ~S is supplied to ~S, it must be ~ + the last argument" + ':eql 'compare-slots)))) + default-var `(let ((,comp-var (compare ,(call (car accs) obj1-var) ,(call (car accs) obj2-var)))) (if (or (eq ,comp-var ':less) (eq ,comp-var ':greater)) @@ -73,8 +93,9 @@ (t `(funcall ,fn ,arg))))) `(let ((,obj1-var ,obj1) (,obj2-var ,obj2) - (,default-var ':equal)) - ,(rec accessors))))) + (,default-var ,(if (member ':eql accessors) '':unequal '':equal))) + (if (eql ,obj1-var ,obj2-var) ':equal + ,(rec accessors))))))
;;; Abstract classes @@ -324,7 +345,8 @@ (let ((len-a (length a)) (len-b (length b)) (default ':equal)) - (cond ((< len-a len-b) ':less) + (cond ((eq a b) ':equal) + ((< len-a len-b) ':less) ((> len-a len-b) ':greater) ((and (simple-vector-p a) (simple-vector-p b)) (dotimes (i len-a default) @@ -359,6 +381,8 @@ (if (or (eq comp ':less) (eq comp ':greater)) comp default))) + (when (eq a b) ; we could get lucky + (return default)) (let ((comp (compare (car a) (car b)))) (when (or (eq comp ':less) (eq comp ':greater)) (return comp)) @@ -412,49 +436,53 @@ can be strings, vectors, lists, or seqs."))
(defmethod compare-lexicographically ((a string) (b string)) - (let ((len-a (length a)) - (len-b (length b))) - (if (and (simple-string-p a) (simple-string-p b)) + (if (eq a b) + ':equal + (let ((len-a (length a)) + (len-b (length b))) + (if (and (simple-string-p a) (simple-string-p b)) + (dotimes (i (min len-a len-b) + (cond ((< len-a len-b) ':less) + ((> len-a len-b) ':greater) + (t ':equal))) + (let ((ca (schar a i)) + (cb (schar b i))) + (cond ((char< ca cb) (return ':less)) + ((char> ca cb) (return ':greater))))) (dotimes (i (min len-a len-b) (cond ((< len-a len-b) ':less) ((> len-a len-b) ':greater) (t ':equal))) - (let ((ca (schar a i)) - (cb (schar b i))) + (let ((ca (char a i)) + (cb (char b i))) (cond ((char< ca cb) (return ':less)) - ((char> ca cb) (return ':greater))))) - (dotimes (i (min len-a len-b) - (cond ((< len-a len-b) ':less) - ((> len-a len-b) ':greater) - (t ':equal))) - (let ((ca (char a i)) - (cb (char b i))) - (cond ((char< ca cb) (return ':less)) - ((char> ca cb) (return ':greater)))))))) + ((char> ca cb) (return ':greater)))))))))
(defmethod compare-lexicographically ((a list) (b list)) (compare-lists-lexicographically a b))
(defmethod compare-lexicographically ((a vector) (b vector)) - (let ((len-a (length a)) - (len-b (length b)) - (default ':equal)) - (if (and (simple-vector-p a) (simple-vector-p b)) + (if (eq a b) + ':equal + (let ((len-a (length a)) + (len-b (length b)) + (default ':equal)) + (if (and (simple-vector-p a) (simple-vector-p b)) + (dotimes (i (min len-a len-b) + (cond ((< len-a len-b) ':less) + ((> len-a len-b) ':greater) + (t default))) + (let ((res (compare (svref a i) (svref b i)))) + (when (or (eq res ':less) (eq res ':greater)) + (return res)) + (when (eq res ':unequal) + (setq default ':unequal)))) (dotimes (i (min len-a len-b) (cond ((< len-a len-b) ':less) ((> len-a len-b) ':greater) (t default))) - (let ((res (compare (svref a i) (svref b i)))) + (let ((res (compare (aref a i) (aref b i)))) (when (or (eq res ':less) (eq res ':greater)) (return res)) (when (eq res ':unequal) - (setq default ':unequal)))) - (dotimes (i (min len-a len-b) - (cond ((< len-a len-b) ':less) - ((> len-a len-b) ':greater) - (t default))) - (let ((res (compare (aref a i) (aref b i)))) - (when (or (eq res ':less) (eq res ':greater)) - (return res)) - (when (eq res ':unequal) - (setq default ':unequal))))))) + (setq default ':unequal))))))))
Modified: trunk/Code/reader.lisp ============================================================================== --- trunk/Code/reader.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/reader.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -258,10 +258,11 @@ argument subforms. Each argument subform can be a list of the form (`key-expr' `value-expr'), denoting a mapping from the value of `key-expr' to the value of `value-expr'; or a list of the form ($ `expression'), in which case the -expression must evaluate to a map, denoting all its mappings. The result is -constructed from the denoted mappings in left-to-right order; so if a given key -is supplied by more than one argument subform, its associated value will be -given by the rightmost such subform." +expression must evaluate to a map, denoting all its mappings; or the symbol +`:default', in which case the next argument subform is a form whose value will +become the map's default. The result is constructed from the denoted mappings +in left-to-right order; so if a given key is supplied by more than one argument +subform, its associated value will be given by the rightmost such subform." (expand-map-constructor-form 'map args))
(defmacro wb-map (&rest args) @@ -269,18 +270,23 @@ argument subform can be a list of the form (`key-expr' `value-expr'), denoting a mapping from the value of `key-expr' to the value of `value-expr'; or a list of the form ($ `expression'), in which case the expression must evaluate to a -map, denoting all its mappings. The result is constructed from the denoted -mappings in left-to-right order; so if a given key is supplied by more than -one argument subform, its associated value will be given by the rightmost such -subform." +map, denoting all its mappings; or the symbol `:default', in which case the +next argument subform is a form whose value will become the map's default. The +result is constructed from the denoted mappings in left-to-right order; so if a +given key is supplied by more than one argument subform, its associated value +will be given by the rightmost such subform." (expand-map-constructor-form 'wb-map args))
(defun expand-map-constructor-form (type-name args) (let ((empty-form (ecase type-name (map `(empty-map)) - (wb-map `(empty-wb-map))))) + (wb-map `(empty-wb-map)))) + (default nil)) (labels ((recur (args result) (cond ((null args) result) + ((eq (car args) ':default) + (setq default (cadr args)) + (recur (cddr args) result)) ((not (and (listp (car args)) (= (length (car args)) 2))) (error "Arguments to ~S must all be pairs expressed as 2-element~@ @@ -292,7 +298,7 @@ (recur (cdr args) `(map-union ,result ,(cadar args))))) (t (recur (cdr args) `(with ,result ,(caar args) ,(cadar args))))))) - (recur args empty-form)))) + `(with-default ,(recur args empty-form) ,default))))
(defmacro seq (&rest args) "Constructs a seq of the default implementation according to the supplied
Modified: trunk/Code/relations.lisp ============================================================================== --- trunk/Code/relations.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/relations.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -75,6 +75,11 @@ (and found? (WB-Set-Tree-Member? set-tree (cdr pr)))))
;;; Returns the range set. +;;; &&& Aaagh -- not sure this makes sense -- (setf (lookup rel x) ...) doesn't do +;;; the right thing at all, relative to this. Maybe the setf expander for `lookup'/`@' +;;; should call an internal form of `with' that does something different on a +;;; relation... Yes, I think this operation should be renamed, and `setf-lookup' +;;; should not exist on a relation, as `lookup' should not. (defmethod lookup ((br wb-2-relation) x) (let ((found? set-tree (WB-Map-Tree-Lookup (wb-2-relation-map0 br) x))) (if found? (make-wb-set set-tree) @@ -189,17 +194,17 @@ (defmethod union ((br1 wb-2-relation) (br2 wb-2-relation) &key) (let ((new-size 0) ((new-map0 (WB-Map-Tree-Union (wb-2-relation-map0 br1) (wb-2-relation-map0 br2) - (lambda (ignore s1 s2) - (declare (ignore ignore)) + (lambda (s1 s2) (let ((s (WB-Set-Tree-Union s1 s2))) (incf new-size (WB-Set-Tree-Size s)) s)))) (new-map1 (and (or (wb-2-relation-map1 br1) (wb-2-relation-map1 br2)) - (WB-Map-Tree-Union (wb-2-relation-map1 br1) - (wb-2-relation-map1 br2) - (lambda (ignore s1 s2) - (declare (ignore ignore)) - (WB-Set-Tree-Union s1 s2))))))) + (progn + (get-inverse br1) + (get-inverse br2) + (WB-Map-Tree-Union (wb-2-relation-map1 br1) + (wb-2-relation-map1 br2) + #'WB-Set-Tree-Union)))))) (make-wb-2-relation new-size new-map0 new-map1)))
(defmethod intersection ((br1 wb-2-relation) (br2 wb-2-relation) &key) @@ -210,14 +215,14 @@ (declare (ignore ignore)) (let ((s (WB-Set-Tree-Intersect s1 s2))) (incf new-size (WB-Set-Tree-Size s)) - (values s s))))) + s)))) (new-map1 (and (or (wb-2-relation-map1 br1) (wb-2-relation-map1 br2)) - (WB-Map-Tree-Intersect (wb-2-relation-map1 br1) - (wb-2-relation-map1 br2) - (lambda (ignore s1 s2) - (declare (ignore ignore)) - (let ((s (WB-Set-Tree-Intersect s1 s2))) - (values s s)))))))) + (progn + (get-inverse br1) + (get-inverse br2) + (WB-Map-Tree-Intersect (wb-2-relation-map1 br1) + (wb-2-relation-map1 br2) + #'WB-Set-Tree-Intersect)))))) (make-wb-2-relation new-size new-map0 new-map1)))
(defgeneric join (relation-a column-a relation-b column-b) @@ -268,6 +273,35 @@ (make-wb-2-relation new-size new-map0 new-map1)))
+(defmethod compose ((rel wb-2-relation) (fn function)) + (2-relation-fn-compose rel fn)) + +(defmethod compose ((rel wb-2-relation) (fn symbol)) + (2-relation-fn-compose rel (coerce fn 'function))) + +(defmethod compose ((rel wb-2-relation) (fn map)) + (2-relation-fn-compose rel fn)) + +(defmethod compose ((rel wb-2-relation) (fn seq)) + (2-relation-fn-compose rel fn)) + +(defmethod compose ((rel1 wb-2-relation) (rel2 wb-2-relation)) + (join rel1 1 rel2 0)) + +(defun 2-relation-fn-compose (rel fn) + (let ((new-size 0) + ((new-map0 (gmap :wb-map (lambda (x ys) + (let ((result nil)) + (Do-WB-Set-Tree-Members (y ys) + (setq result (WB-Set-Tree-With result (@ fn y)))) + (incf new-size (WB-Set-Tree-Size result)) + (values x result))) + (:wb-map (make-wb-map (wb-2-relation-map0 rel))))))) + (make-wb-2-relation new-size + (wb-map-contents new-map0) + nil))) + + (defgeneric internal-do-2-relation (br elt-fn value-fn))
(defmacro do-2-relation ((key val br &optional value) &body body) @@ -293,6 +327,13 @@ (setq result (WB-Set-Tree-With result (funcall pair-fn x y)))) (make-wb-set result)))
+;;; I've made the default conversions between maps and 2-relations use the +;;; same pairs; that is, the conversion from a map to a 2-relation yields a +;;; functional relation with the same mappings, and the inverse conversion +;;; requires a functional relation and yields a map with the same mappings. +;;; This is mathematically elegant, but I wonder if the other kind of conversion +;;; -- where the map's range is set-valued -- is not more useful in practice, +;;; and maybe more deserving of being the default. (defmethod convert ((to-type (eql '2-relation)) (m map) &key from-type) "If `from-type' is the symbol `map-to-sets', the range elements must all be sets, and the result pairs each domain element with each member of the @@ -351,9 +392,17 @@ (make-wb-2-relation size m0 nil)))
(defmethod convert ((to-type (eql 'map)) (br wb-2-relation) &key) + "This conversion requires the relation to be functional, and returns +a map representing the function; that is, the relation must map each +domain value to a single range value, and the returned map maps that +domain value to that range value." (2-relation-to-wb-map br))
(defmethod convert ((to-type (eql 'wb-map)) (br wb-2-relation) &key) + "This conversion requires the relation to be functional, and returns +a map representing the function; that is, the relation must map each +domain value to a single range value, and the returned map maps that +domain value to that range value." (2-relation-to-wb-map br))
(defun 2-relation-to-wb-map (br) @@ -365,6 +414,11 @@ (setq m (WB-Map-Tree-With m x (WB-Set-Tree-Arb s))))) (make-wb-map m)))
+(defmethod convert ((to-type (eql 'map-to-sets)) (br wb-2-relation) &key) + "This conversion returns a map mapping each domain value to the set of +corresponding range values." + (make-wb-map (WB-Map-Tree-Compose (wb-2-relation-map0 br) #'make-wb-set))) + (defgeneric conflicts (2-relation) (:documentation "Returns a 2-relation containing only those pairs of `2-relation' whose domain value @@ -398,6 +452,36 @@ (format stream " "))) (format stream "+}"))))
+(defmethod iterator ((rel wb-2-relation) &key) + (let ((outer (Make-WB-Map-Tree-Iterator-Internal (wb-2-relation-map0 rel))) + (cur-dom-elt nil) + (inner nil)) + (lambda (op) + (ecase op + (:get (if (WB-Map-Tree-Iterator-Done? outer) + (values nil nil nil) + (progn + (when (or (null inner) (WB-Set-Tree-Iterator-Done? inner)) + (let ((dom-elt inner-tree (WB-Map-Tree-Iterator-Get outer))) + (setq cur-dom-elt dom-elt) + (assert inner-tree) ; must be nonempty + (setq inner (Make-WB-Set-Tree-Iterator-Internal inner-tree)))) + (values cur-dom-elt (WB-Set-Tree-Iterator-Get inner) t)))) + (:done? (WB-Map-Tree-Iterator-Done? outer)) + (:more? (not (WB-Map-Tree-Iterator-Done? outer))))))) + +(def-gmap-arg-type :2-relation (rel) + "Yields each pair of `rel', as two values." + `((iterator ,rel) + #'(lambda (it) (declare (type function it)) (funcall it ':done?)) + (:values 2 #'(lambda (it) (declare (type function it)) (funcall it ':get))))) + +(def-gmap-arg-type :wb-2-relation (rel) + "Yields each pair of `rel', as two values." + `((iterator ,rel) + #'(lambda (it) (declare (type function it)) (funcall it ':done?)) + (:values 2 #'(lambda (it) (declare (type function it)) (funcall it ':get))))) + (def-gmap-res-type :2-relation (&key filterp) "Consumes two values from the mapped function; returns a 2-relation of the pairs. Note that `filterp', if supplied, must take two arguments." @@ -460,7 +544,7 @@ (set-transitive-closure r s))
(defun set-transitive-closure (r s) - ;; This could probably use a little moer work. + ;; This could probably use a little more work. (let ((workset (set-difference (reduce #'union (image r (convert 'seq s)) :initial-value (set)) s)) @@ -471,3 +555,589 @@ (adjoinf result x) (unionf workset (set-difference (@ r x) result)))) result)) + + +(defmacro 2-relation (&rest args) + "Constructs a 2-relation of the default implementation according to the supplied +argument subforms. Each argument subform can be a list of the form (`key-expr' +`value-expr'), denoting a mapping from the value of `key-expr' to the value of +`value-expr'; or a list of the form ($ `expression'), in which case the +expression must evaluate to a 2-relation, all of whose mappings will be +included in the result." + (expand-2-relation-constructor-form '2-relation args)) + +(defmacro wb-2-relation (&rest args) + "Constructs a wb-2-relation according to the supplied argument subforms. +Each argument subform can be a list of the form (`key-expr' `value-expr'), +denoting a mapping from the value of `key-expr' to the value of `value-expr'; +or a list of the form ($ `expression'), in which case the expression must +evaluate to a 2-relation, all of whose mappings will be included in the +result." + (expand-2-relation-constructor-form '2-relation args)) + +(defun expand-2-relation-constructor-form (type-name args) + (let ((empty-form (ecase type-name + (2-relation '(empty-2-relation)) + (wb-2-relation '(empty-wb-2-relation))))) + (labels ((recur (args result) + (cond ((null args) result) + ((not (and (listp (car args)) + (= (length (car args)) 2))) + (error "Arguments to ~S must all be pairs expressed as 2-element~@ + lists, or ($ x) subforms -- not ~S" + type-name (car args))) + ((eq (caar args) '$) + (if (eq result empty-form) + (recur (cdr args) (cadar args)) + (recur (cdr args) `(union ,result ,(cadar args))))) + (t + (recur (cdr args) `(with ,result ,(caar args) ,(cadar args))))))) + (recur args empty-form)))) + + +;;; ================================================================================ +;;; List relations + +;;; A list relation is a general relation (i.e. of arbitrary arity >= 2) whose +;;; tuples are in list form. List relations support a `query' operation that +;;; takes, along with the relation, two lists, each of length equal to the +;;; arity, called the "pattern" and "metapattern". For each position, if the +;;; metapattern contains `nil', the query is not constrained by that position +;;; (the corresponding position in the pattern is ignored); if the metapattern +;;; contains `t' or `:single', then the result set contains only those tuples +;;; with the same value in that position as the pattern has. The difference +;;; between `t' and `:single' has to do with indexing. For each metapattern +;;; that is actually used, an index is constructed if not previously present, +;;; and then is maintained incrementally. If the metapattern has `t' in a +;;; location, the resulting index will contain all values for that location; +;;; if it has `:single', the resulting index will contain only those values +;;; that have actually appeared in a query pattern with this metapattern. + + + +(defstruct (list-relation + (:include relation) + (:constructor nil) + (:predicate list-relation?) + (:copier nil)) + "The abstract class for FSet list relations. It is a structure class. +A list relation is a general relation (i.e. of arbitrary arity >= 2) whose +tuples are in list form.") + +(defstruct (wb-list-relation + (:include list-relation) + (:constructor make-wb-list-relation (arity tuples indices)) + (:predicate wb-list-relation?) + (:print-function print-wb-list-relation) + (:copier nil)) + "A class of functional relations of arbitrary arity >= 2, whose tuples +are in list form." + arity + tuples + ;; a map from augmented metapattern to map from reduced tuple to set of tuples + indices) + + +(defun empty-list-relation (&optional arity) + "We allow the arity to be temporarily unspecified; it will be taken from +the first tuple added, or the first query." + (unless (or (null arity) (and (integerp arity) (>= arity 1))) + (error "Invalid arity")) + (empty-wb-list-relation arity)) + +(defun empty-wb-list-relation (arity) + "We allow the arity to be temporarily unspecified; it will be taken from +the first tuple added, or the first query." + ;; If arity = 1 it's just a set... but what the heck... + (unless (or (null arity) (and (integerp arity) (>= arity 1))) + (error "Invalid arity")) + (make-wb-list-relation arity (set) (map))) + +(defmethod arity ((rel wb-list-relation)) + "Will return `nil' if the arity is not yet specified; see `empty-list-relation'." + (wb-list-relation-arity rel)) + +(defmethod empty? ((rel wb-list-relation)) + (empty? (wb-list-relation-tuples rel))) + +(defmethod size ((rel wb-list-relation)) + (size (wb-list-relation-tuples rel))) + +(defmethod arb ((rel wb-list-relation)) + (arb (wb-list-relation-tuples rel))) + +(defmethod contains? ((rel wb-list-relation) tuple) + (contains? (wb-list-relation-tuples rel) tuple)) + +(defgeneric query (relation pattern metapattern) + (:documentation + "Along with the relation, takes two lists, each of length equal to the +arity, called the `pattern' and `metapattern'; returns a set of tuples +satisfying the query. For each position, if the metapattern contains `nil', +the query is not constrained by that position (the corresponding position in +the pattern is ignored); if the metapattern contains `t' or `:single', then +the result set contains only those tuples with the same value in that +position as the pattern has. The difference between `t' and `:single' has +to do with indexing. For each metapattern that is actually used, an index +is constructed if not previously present, and then is maintained +incrementally. If the metapattern has `t' in a location, the resulting +index will contain all values for that location; if it has `:single', the +resulting index will contain only those values that have actually appeared +in a query pattern with this metapattern.")) + +;;; `:single' is implemented, but not necessarily well enough that you'd want to +;;; use it. +(defmethod query ((rel wb-list-relation) (pattern list) (metapattern list)) + (let ((arity (wb-list-relation-arity rel))) + (if (null arity) + ;; We don't know the arity yet, which means there are no tuples. + (set) + (progn + (unless (and (= (length pattern) arity) + (= (length metapattern) arity)) + (error "Pattern or metapattern is of the wrong length")) + (if (every #'identity metapattern) + (if (contains? rel pattern) (set pattern) (set)) + (let ((augmented-mp (augmented-mp pattern metapattern)) + ((reduced-tuple (reduced-tuple pattern augmented-mp)) + (index (@ (wb-list-relation-indices rel) augmented-mp)))) + (if index + (@ index reduced-tuple) + (progn + + (let ((index-results + (remove nil (mapcar (lambda (index mp-elt pat-elt) + (and index + (@ index (and (eq mp-elt t) + (list pat-elt))))) + (get-indices rel augmented-mp) + augmented-mp pattern)))) + ;; &&& We also want to build composite indices under some + ;; circumstances -- e.g. if the result set is much smaller + ;; than the smallest of `index-results'. + (if index-results + (reduce #'intersection + (sort index-results #'> :key #'size)) + (wb-list-relation-tuples rel))))))))))) + +;;; &&& Another nail in the coffin of `:single'... should just rip it out... +(defgeneric query-multi (rel pattern metapattern) + (:documentation + "Like `query' (q.v.), except that `pattern' is a list of sets of values +rather than a list of values. Returns all tuples in the relation for which +each value is a member of the corresponding set in the pattern. `:single' +in the metapattern is not accepted.")) + +(defmethod query-multi ((rel wb-list-relation) (pattern list) (metapattern list)) + (let ((arity (wb-list-relation-arity rel))) + (if (null arity) + ;; We don't know the arity yet, which means there are no tuples. + (set) + (progn + (unless (and (= (length pattern) arity) + (= (length metapattern) arity)) + (error "Pattern or metapattern is of the wrong length")) + ;; Without :single, the augmented-mp is just the metapattern. + (when (member ':single metapattern) + (error "~S doesn't take ~S" 'query-multi ':single)) + (if (every (fn (s) (= (size s) 1)) pattern) + (query rel (mapcar #'arb pattern) metapattern) + (let ((index-results + (remove nil + (mapcar (lambda (index pat-elt) + (and index + (gmap :union + (fn (pat-elt-elt) + (@ index (list pat-elt-elt))) + (:set pat-elt)))) + (get-indices rel metapattern) + pattern)))) + (if index-results + (reduce #'intersection + (sort index-results #'> :key #'size)) + (wb-list-relation-tuples rel)))))))) + +(defun get-indices (rel augmented-mp) + "Returns a list giving the index to use for each element of `augmented-mp'." + (flet ((make-mp (i elt) + (let ((mp nil) + (arity (wb-list-relation-arity rel))) + (dotimes (j arity) + (push (and (= i (- arity j 1)) elt) mp)) + mp))) + ;; First we see what indices exist on each position. + (let ((ex-inds (gmap :list + (lambda (mp-elt i) + (and mp-elt (or (@ (wb-list-relation-indices rel) + (make-mp i mp-elt)) + (and (not (eq mp-elt t)) + (@ (wb-list-relation-indices rel) + (make-mp i t)))))) + (:list augmented-mp) + (:index 0))) + ((unindexed (mapcar (lambda (index mp-elt) + (and (null index) mp-elt)) + ex-inds augmented-mp)))) + ;; Now, if there were any instantiated positions for which an index did + ;; not exist, construct indices for them. + (unless (every #'null unindexed) + (let ((saved-mps (gmap :list (lambda (unind i) + (and unind (make-mp i unind))) + (:list unindexed) + (:index 0))) + (new-indices (make-array (length augmented-mp) + :initial-element (empty-map (set))))) + (do-set (tuple (wb-list-relation-tuples rel)) + (gmap nil (lambda (tuple-elt unind saved-mp i) + (when (and unind + (or (eq unind t) + (equal? tuple-elt (cdr unind)))) + (adjoinf (@ (svref new-indices i) + (reduced-tuple tuple saved-mp)) + tuple))) + (:list tuple) + (:list unindexed) + (:list saved-mps) + (:index 0))) + (gmap nil (lambda (saved-mp new-index) + (when saved-mp + (setf (@ (wb-list-relation-indices rel) saved-mp) new-index))) + (:list saved-mps) + (:vector new-indices)) + (setq ex-inds (gmap :list (lambda (ex-ind saved-mp new-index) + (or ex-ind (and saved-mp new-index))) + (:list ex-inds) + (:list saved-mps) + (:vector new-indices))))) + ;; &&& If we just built a complete index that subsumes any single-value indices, + ;; need to discard the latter. + ;; &&& Also, if the total size of the single-value indices we build for any + ;; position gets large enough, we should replace them all with a complete index. + ex-inds))) + +(defmethod with ((rel wb-list-relation) tuple &optional (arg2 nil arg2?)) + (declare (ignore arg2)) + (check-two-arguments arg2? 'with 'wb-list-relation) + (let ((arity (or (wb-list-relation-arity rel) + (length tuple)))) + (unless (and (listp tuple) (= (length tuple) arity)) + (error "Length of tuple, ~D, does not equal arity, ~D" + (length tuple) arity)) + (if (contains? (wb-list-relation-tuples rel) tuple) + rel + (make-wb-list-relation arity (with (wb-list-relation-tuples rel) tuple) + ;; Hmm, methinks we need to index the index map... + (image (lambda (aug-mp rt-map) + (if (augmented-mp-matches? aug-mp tuple) + (let ((rt (reduced-tuple tuple aug-mp))) + (values aug-mp + (with rt-map rt + (with (@ rt-map rt) tuple)))) + (values aug-mp rt-map))) + (wb-list-relation-indices rel)))))) + +(defmethod less ((rel wb-list-relation) tuple &optional (arg2 nil arg2?)) + (declare (ignore arg2)) + (check-two-arguments arg2? 'with 'wb-list-relation) + (let ((arity (or (wb-list-relation-arity rel) + (length tuple)))) + (unless (and (listp tuple) (= (length tuple) arity)) + (error "Length of tuple, ~D, does not equal arity, ~D" + (length tuple) arity)) + (if (not (contains? (wb-list-relation-tuples rel) tuple)) + rel + (make-wb-list-relation arity (less (wb-list-relation-tuples rel) tuple) + (image (lambda (aug-mp rt-map) + (if (augmented-mp-matches? aug-mp tuple) + (let ((rt (reduced-tuple tuple aug-mp))) + (values aug-mp + (with rt-map rt + (less (@ rt-map rt) tuple)))) + (values aug-mp rt-map))) + (wb-list-relation-indices rel)))))) + +;;; &&& I suppose that instead of consing these things up all the time we could +;;; have a special pattern object with special compare methods against lists that +;;; would compare only the desired positions. L8r... +(defun reduced-tuple (tuple augmented-mp) + "Returns a list of those members of `tuple' corresponding to instantiated +positions in the original pattern." + (if (every (lambda (x) (eq x t)) augmented-mp) tuple + (gmap (:list :filterp #'identity) ; omits nil + (lambda (pat-elt mp-elt) + (and (eq mp-elt t) pat-elt)) + (:list tuple) + (:list augmented-mp)))) + +(defun augmented-mp (pattern metapattern) + "Returns a list, of the same length as the pattern, which is like the +metapattern except that each `:single' has been replaced by a cons of +`:single' and the corresponding pattern element." + (if (not (member ':single metapattern)) metapattern + (mapcar (lambda (pat-elt mp-elt) + (if (eq mp-elt ':single) (cons ':single pat-elt) + mp-elt)) + pattern metapattern))) + +(defun augmented-mp-matches? (augmented-mp tuple) + (every (lambda (mp-elt tuple-elt) + (or (eq mp-elt nil) (eq mp-elt t) + (and (consp mp-elt) (eq (car mp-elt) ':single) + (equal? tuple-elt (cdr mp-elt))))) + augmented-mp tuple)) + + + +(defgeneric internal-do-list-relation (rel elt-fn value-fn)) + +(defmacro do-list-relation ((tuple rel &optional value) &body body) + `(block nil + (internal-do-list-relation ,rel (lambda (,tuple) . ,body) + (lambda () ,value)))) + +(defmethod internal-do-list-relation ((rel wb-list-relation) elt-fn value-fn) + (Do-WB-Set-Tree-Members (tuple (wb-set-contents (wb-list-relation-tuples rel)) + (funcall value-fn)) + (funcall elt-fn tuple))) + +(defun print-wb-list-relation (rel stream level) + (if (and *print-level* (>= level *print-level*)) + (format stream "#") + (progn + (format stream "#{* ") + (let ((i 0)) + (do-list-relation (tuple rel) + (when (> i 0) + (format stream " ")) + (when (and *print-length* (>= i *print-length*)) + (format stream "...") + (return)) + (incf i) + (let ((*print-level* (and *print-level* (1- *print-level*)))) + (write tuple :stream stream))) + (when (> i 0) + (format stream " "))) + (format stream "*}~@[^~D~]" (arity rel))))) + +#|| + +Okay, this is a start, but: + +() Don't we want to do better meta-indexing, so adding a tuple doesn't require +iterating through all the indices? + +() I'm not creating composite indices yet. The plan is straightforward -- create +one when the size of the final result set is <= the square root of the size of +the smallest index set. This is easy, but how do subsequent queries find the +composite index? + +[Later] I think that for now, the single-value index feature is an unnecessary +complication. Without it, there either exists an index on a column, or not. + +As for composite indices, I think the right way to find them will be with a +discrimination tree (or DAG), but I'm not going to bother with them yet either. + +||# + + +;;; A query registry to be used with `list-relation'. Register queries with +;;; `with-query', supplying a pattern and metapattern. The queries themselves +;;; are uninterpreted except that they are kept in sets (so CL closures are not +;;; a good choice). `lookup' returns the set of queries that match the supplied +;;; tuple. +(defstruct (query-registry + (:constructor make-query-registry (arity indices key-index))) + arity + ;; A map from augmented metapattern to map from reduced tuple to set of queries. + ;; &&& Not worrying for now whether this does anything reasonable with `:single'. + indices + ;; A map from every "key", i.e., value used in an instantiated position in a + ;; pattern, to map from augmented metapattern to set of reduced tuples in which + ;; they were used. + key-index) + +(defun empty-query-registry (&optional arity) + (unless (or (null arity) (and (integerp arity) (>= arity 1))) + (error "Invalid arity")) + (make-query-registry arity (empty-map (empty-map (set))) + (empty-map (empty-map (set))))) + +(defmethod arity ((reg query-registry)) + (query-registry-arity reg)) + +(defmethod with-query ((reg query-registry) (pattern list) (metapattern list) query) + (let ((arity (or (query-registry-arity reg) + (length pattern)))) + (unless (and (= (length pattern) arity) + (= (length metapattern) arity)) + (error "Pattern or metapattern is of the wrong length")) + (let ((augmented-mp (augmented-mp pattern metapattern)) + ((reduced-tuple (reduced-tuple pattern augmented-mp)) + ((prev-1 (@ (query-registry-indices reg) augmented-mp)) + ((prev-2 (@ prev-1 reduced-tuple))) + (aug->red (map (augmented-mp (set reduced-tuple)) :default (set)))))) + (make-query-registry arity + (with (query-registry-indices reg) augmented-mp + (with prev-1 reduced-tuple + (with prev-2 query))) + (map-union (query-registry-key-index reg) + (gmap (:map :default (empty-map (set))) + (fn (key) (values key aug->red)) + (:list reduced-tuple)) + (lambda (x y) (map-union x y #'union))))))) + +(defmethod less-query ((reg query-registry) (pattern list) (metapattern list) query) + (let ((arity (or (query-registry-arity reg) + (length pattern)))) + (unless (and (= (length pattern) arity) + (= (length metapattern) arity)) + (error "Pattern or metapattern is of the wrong length")) + (let ((augmented-mp (augmented-mp pattern metapattern)) + ((reduced-tuple (reduced-tuple pattern augmented-mp)) + ((prev-1 (@ (query-registry-indices reg) augmented-mp)) + ((prev-2 (@ prev-1 reduced-tuple)))))) + (make-query-registry arity + (with (query-registry-indices reg) augmented-mp + (with prev-1 reduced-tuple + (less prev-2 query))) + ;; &&& For now. + (query-registry-key-index reg))))) + +(defmethod all-queries ((reg query-registry)) + (gmap :union (fn (_aug-mp submap) + (gmap :union (fn (_red-tup queries) + queries) + (:map submap))) + (:map (query-registry-indices reg)))) + +(defmethod lookup ((reg query-registry) tuple) + "Returns all queries in `reg' whose patterns match `tuple'." + (let ((arity (or (query-registry-arity reg) + (length tuple)))) + (unless (and (listp tuple) (= (length tuple) arity)) + (error "Length of tuple, ~D, does not match arity, ~D" + (length tuple) arity)) + (gmap :union (lambda (aug-mp rt-map) + (@ rt-map (reduced-tuple tuple aug-mp))) + (:map (query-registry-indices reg))))) + +(defmethod lookup-multi ((reg query-registry) set-tuple) + "Here `set-tuple' contains a set of values in each position. Returns +all queries in `reg' whose patterns match any member of the cartesian +product of the sets." + (let ((arity (or (query-registry-arity reg) + (length set-tuple)))) + (unless (and (listp set-tuple) (= (length set-tuple) arity)) + (error "Length of tuple, ~D, does not match arity, ~D" + (length set-tuple) arity)) + ;; Ugh. At least, computing the cartesian product of the reduced set-tuple + ;; will frequently be faster than computing that of the original. Still, + ;; maybe we &&& need to redesign the indexing scheme here... + (gmap :union (lambda (aug-mp rt-map) + (gmap :union (fn (tuple) + (@ rt-map tuple)) + (:seq (cartesian-product (reduced-tuple set-tuple aug-mp))))) + (:map (query-registry-indices reg))))) + +;;; Since all the members are known to be distinct, we return a seq rather +;;; than pay the setification cost... a little inelegant, though. +(defmethod cartesian-product ((sets list)) + (if (null sets) + (seq nil) + (gmap :concat (fn (tail) + (gmap :seq (fn (x) (cons x tail)) + (:set (car sets)))) + (:seq (cartesian-product (cdr sets)))))) + +(defmethod forward-key ((reg query-registry) from-key to-key) + "Returns a new query-registry in which all queries whose patterns used +`from-key' (in an instantiated position) now use `to-key' in that position +instead." + (let ((key-idx-submap (@ (query-registry-key-index reg) from-key)) + ;; We'll generate garbage maintaining the map, but then the tuple instances + ;; will be shared. + (subst-cache (map))) + (flet ((get-subst (tuple) + (or (@ subst-cache tuple) + (setf (@ subst-cache tuple) + (substitute to-key from-key tuple))))) + (make-query-registry + (query-registry-arity reg) + (image (fn (aug-mp submap) + (let ((red-tups (@ key-idx-submap aug-mp))) + (values aug-mp + (map-union (restrict-not submap red-tups) + (gmap (:map :default (set)) + (fn (tup) + (let ((new-tup (get-subst tup))) + (values new-tup + (union (@ submap tup) + (@ submap new-tup))))) + (:set red-tups)) + #'union)))) + (query-registry-indices reg)) + ;; Hehe, this is fun :-) We need to update the indices for the other + ;; keys that occur along with `from-key' in tuples, and we don't want to + ;; walk the whole index to find them; but we already know what tuples are + ;; affected (the ones in `key-idx-submap'), so we work off of that. Doing + ;; this functionally was interesting. + (map-union (reduce (fn (kidx aug-mp tups) + (let ((to-update + (reduce (fn (x y) (map-union x y #'union)) + (image (fn (tup) + (gmap :map + (fn (x) (values x (set tup))) + (:set (less (convert 'set tup) + from-key)))) + tups)))) + (reduce (fn (kidx key tups) + (let ((prev-1 (@ kidx key)) + ((prev-2 (@ prev-1 aug-mp)))) + (with kidx key + (with prev-1 aug-mp + (union (set-difference prev-2 tups) + (image #'get-subst + tups)))))) + to-update :initial-value kidx))) + key-idx-submap + :initial-value (less (query-registry-key-index reg) from-key)) + (map (to-key (compose key-idx-submap + (fn (tups) + (image #'get-subst tups)))) + :default (empty-map (set))) + (fn (x y) (map-union x y #'union))))))) + +(defmethod lookup-restricted ((reg query-registry) tuple key) + "Returns all queries in `reg' whose patterns match `tuple' and which use +`key' (in an instantiated position) in their patterns." + (let ((arity (or (query-registry-arity reg) + (length tuple)))) + (unless (and (listp tuple) (= (length tuple) arity)) + (error "Length of tuple, ~D, does not match arity, ~D" + (length tuple) arity)) + (gmap :union (lambda (aug-mp rt-map) + (@ rt-map (reduced-tuple tuple aug-mp))) + (:map (let ((key-idx-submap (@ (query-registry-key-index reg) key))) + (image (fn (aug-mp rt-map) + (values aug-mp (restrict rt-map (@ key-idx-submap aug-mp)))) + (query-registry-indices reg))))))) + +(defmethod lookup-multi-restricted ((reg query-registry) set-tuple keys) + "Here `set-tuple' contains a set of values in each position. Returns +all queries in `reg' whose patterns match any member of the cartesian +product of the sets and which use a member of `keys' in their patterns." + (let ((arity (or (query-registry-arity reg) + (length set-tuple)))) + (unless (and (listp set-tuple) (= (length set-tuple) arity)) + (error "Length of tuple, ~D, does not match arity, ~D" + (length set-tuple) arity)) + (gmap :union (lambda (aug-mp rt-map) + (gmap :union (fn (tuple) + (@ rt-map tuple)) + (:seq (cartesian-product (reduced-tuple set-tuple aug-mp))))) + (:map (let ((key-idx-submap + (reduce (fn (x y) (map-union x y #'union)) + (image (query-registry-key-index reg) keys)))) + (image (fn (aug-mp rt-map) + (values aug-mp (restrict rt-map (@ key-idx-submap aug-mp)))) + (query-registry-indices reg)))))))
Modified: trunk/Code/testing.lisp ============================================================================== --- trunk/Code/testing.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/testing.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -122,8 +122,11 @@ (test (unequal? (seq 'a 3 'c) (seq 'a 3.0 'c))) (test (less-than? (seq 'a 3 'c) (seq 'a 3.0 'd))) (test (less-than? (seq) (tuple))) - (test (equal (convert 'list (eval '(tuple (+K0+ 1) ($ (tuple (+K1+ 2) (+K2+ 3))) - (+K0+ 2) ($ (tuple (+K4+ 7) (+K2+ 8)))))) + (test (equal (sort (convert 'list (eval '(tuple (+K0+ 1) + ($ (tuple (+K1+ 2) (+K2+ 3))) + (+K0+ 2) + ($ (tuple (+K4+ 7) (+K2+ 8)))))) + #'< :key (fn (x) (tuple-key-number (car x)))) `((,+K0+ . 2) (,+K1+ . 2) (,+K2+ . 8) (,+K4+ . 7)))) (test (less-than? (tuple (+K0+ 1)) (tuple (+K0+ 2)))) (test (unequal? (tuple (+K0+ 1.0) (+K1+ 'c)) (tuple (+K0+ 1) (+K1+ 'c))))
Modified: trunk/Code/tuples.lisp ============================================================================== --- trunk/Code/tuples.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/tuples.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -52,7 +52,7 @@ ;;; with sparse slots (at which most of the tuples created have no assigned ;;; value), you may find the additional functionality of these tuples useful.
-;;; Keys can be defined with `def-tuple-key', or obtained at runtime with +;;; Keys can be defined with `define-tuple-key', or obtained at runtime with ;;; `get-tuple-key'.
;;; The implementation gets its speed by arranging for lookup to be done by @@ -148,6 +148,11 @@ (error "Tuple key space exhausted"))))))
(defmacro def-tuple-key (name &optional default-fn) + "Deprecated; use `define-tuple-key'." + ;; What this should have been called to begin with. + `(define-tuple-key ,name ,default-fn)) + +(defmacro define-tuple-key (name &optional default-fn) "Defines a tuple key named `name' as a global lexical variable (see `deflex'). If `default-fn' is supplied, it is used to compute a value for lookups where the tuple has no explicit pair with this key; it is called with one argument,
Modified: trunk/Code/wb-trees.lisp ============================================================================== --- trunk/Code/wb-trees.lisp Sun Nov 9 21:44:59 2008 (r26) +++ trunk/Code/wb-trees.lisp Sat Nov 12 21:21:18 2011 (r27) @@ -4953,21 +4953,22 @@
(defun WB-Map-Tree-Compose (tree fn) - (if (consp tree) - (cons (car tree) - (gmap (:vector :length (length (cdr tree))) - fn (:simple-vector (cdr tree)))) - (let ((key (WB-Map-Tree-Node-Key tree)) - (val (WB-Map-Tree-Node-Value tree)) - (new-left (WB-Map-Tree-Compose (WB-Map-Tree-Node-Left tree) fn)) - (new-right (WB-Map-Tree-Compose (WB-Map-Tree-Node-Right tree) fn))) - (if (Equivalent-Map? key) - (Make-WB-Map-Tree-Node - (Make-Equivalent-Map (mapcar (lambda (pr) - (cons (car pr) (funcall fn (cdr pr)))) - (Equivalent-Map-Alist key))) - val new-left new-right) - (Make-WB-Map-Tree-Node key (funcall fn val) new-left new-right))))) + (and tree + (if (consp tree) + (cons (car tree) + (gmap (:vector :length (length (cdr tree))) + fn (:simple-vector (cdr tree)))) + (let ((key (WB-Map-Tree-Node-Key tree)) + (val (WB-Map-Tree-Node-Value tree)) + (new-left (WB-Map-Tree-Compose (WB-Map-Tree-Node-Left tree) fn)) + (new-right (WB-Map-Tree-Compose (WB-Map-Tree-Node-Right tree) fn))) + (if (Equivalent-Map? key) + (Make-WB-Map-Tree-Node + (Make-Equivalent-Map (mapcar (lambda (pr) + (cons (car pr) (funcall fn (cdr pr)))) + (Equivalent-Map-Alist key))) + val new-left new-right) + (Make-WB-Map-Tree-Node key (funcall fn val) new-left new-right))))))
;;; ---------------- @@ -5677,61 +5678,59 @@ (push (Make-WB-Seq-Tree-Node left right) stack))))))))
(defun WB-Seq-Tree-To-Vector (tree) + (let ((result (make-array (WB-Seq-Tree-Size tree)))) + (labels ((fillr (tree result idx) + (declare (optimize (speed 3) (safety 0)) + (fixnum idx)) + (cond ((stringp tree) + (dotimes (i (length (the simple-string tree))) + (setf (svref result (+ idx i)) (schar tree i)))) + ((simple-vector-p tree) + (dotimes (i (length tree)) + (setf (svref result (+ idx i)) (svref tree i)))) + (t + (let ((left (WB-Seq-Tree-Node-Left tree))) + (fillr left result idx) + (fillr (WB-Seq-Tree-Node-Right tree) + result (+ idx (WB-Seq-Tree-Size left)))))))) + (fillr tree result 0) + result))) + +(defun WB-Seq-Tree-To-String (tree) (declare (optimize (speed 3) (safety 0))) - (if (or (null tree) (simple-vector-p tree)) - (coerce tree 'vector) + (if (null tree) "" (labels ((element-type (tree) (cond ((null tree) 'base-char) ((vectorp tree) (cond ((typep tree 'base-string) 'base-char) #+FSet-Ext-Strings ((stringp tree) 'character) - (t t))) + (t + (error 'type-error + :datum (find-if-not #'characterp tree) + :expected-type 'character)))) (t (let ((left (element-type (WB-Seq-Tree-Node-Left tree))) (right (element-type (WB-Seq-Tree-Node-Right tree)))) - (cond ((or (eq left t) (eq right t)) - t) - #+FSet-Ext-Strings + (cond #+FSet-Ext-Strings ((or (eq left 'character) (eq right 'character)) 'character) (t 'base-char))))))) (let ((elt-type (element-type tree))) - (if (member elt-type '(base-char character)) - (let ((result (make-string (WB-Seq-Tree-Size tree) :element-type elt-type))) - (labels ((fillr (tree result idx) - (declare (optimize (speed 3) (safety 0)) - (fixnum idx)) - (cond ((stringp tree) - (dotimes (i (length (the simple-string tree))) - ;; All this code duplication is just so we can use - ;; `(schar result ...)' here and `(svref result ...)' - ;; below. - (setf (schar result (+ idx i)) (schar tree i)))) - (t - (let ((left (WB-Seq-Tree-Node-Left tree))) - (fillr left result idx) - (fillr (WB-Seq-Tree-Node-Right tree) - result (+ idx (WB-Seq-Tree-Size left)))))))) - (fillr tree result 0) - result)) - (let ((result (make-array (WB-Seq-Tree-Size tree)))) - (labels ((fillr (tree result idx) - (declare (optimize (speed 3) (safety 0)) - (fixnum idx)) - (cond ((stringp tree) - (dotimes (i (length (the simple-string tree))) - (setf (svref result (+ idx i)) (schar tree i)))) - ((simple-vector-p tree) - (dotimes (i (length tree)) - (setf (svref result (+ idx i)) (svref tree i)))) - (t - (let ((left (WB-Seq-Tree-Node-Left tree))) - (fillr left result idx) - (fillr (WB-Seq-Tree-Node-Right tree) - result (+ idx (WB-Seq-Tree-Size left)))))))) - (fillr tree result 0) - result))))))) + (let ((result (make-string (WB-Seq-Tree-Size tree) :element-type elt-type))) + (labels ((fillr (tree result idx) + (declare (optimize (speed 3) (safety 0)) + (fixnum idx)) + (cond ((stringp tree) + (dotimes (i (length (the simple-string tree))) + (setf (schar result (+ idx i)) (schar tree i)))) + (t + (let ((left (WB-Seq-Tree-Node-Left tree))) + (fillr left result idx) + (fillr (WB-Seq-Tree-Node-Right tree) + result (+ idx (WB-Seq-Tree-Size left)))))))) + (fillr tree result 0) + result))))))
;;; ================================================================================