From 4949109622420209faeb81b92ce9b75a9f5c52aa Mon Sep 17 00:00:00 2001 From: "Scott L. Burson" Date: Fri, 17 May 2024 15:08:58 -0700 Subject: [PATCH] Fix 'intersection' on binary relations. Also adds ':no-value' feature to 'map-intersection' (if 'val-fn' returns a second value of ':no-value', the pair is omitted from the result entirely). The 'map-union' method had this feature already, but it was incompletely implemented and buggy; it is now believed fixed. --- Code/fset.lisp | 31 ++++++++-- Code/relations.lisp | 6 +- Code/testing.lisp | 110 +++++++++++++++++++++++++++------- Code/wb-trees.lisp | 140 ++++++++++++++++++++++++++------------------ 4 files changed, 203 insertions(+), 84 deletions(-) diff --git a/Code/fset.lisp b/Code/fset.lisp index 07e7c12..d4757e2 100644 --- a/Code/fset.lisp +++ b/Code/fset.lisp @@ -293,6 +293,10 @@ when the supplied key or index is not in the domain.")) "Returns a new map or seq with the same contents as `collection' but whose default is now `new-default'.")) +;;; Prior to 1.4.0, this unconditionally called `val-fn' on the defaults of the +;;; two maps to produce the defaults of the result. This was a PITA in the +;;; common case in which the defaults were null. Now, it calls `val-fn' only +;;; if at least one of the defaults is nonnull. (defgeneric map-union (map1 map2 &optional val-fn) (:documentation "Returns a map containing all the keys of `map1' and `map2', where the @@ -301,12 +305,18 @@ the value for each key contained in both maps is the result of calling `val-fn' on the value from `map1' and the value from `map2'. `val-fn' defaults to simply returning its second argument, so the entries in `map2' simply shadow those in `map1'. The default for the new map is the result of -calling `val-fn' on the defaults for the two maps (so be sure it can take -these values). +calling `val-fn' on the defaults for the two maps, if either of those is +nonnull. `map-union' assumes that if the two values passed to `val-fn' +are equal, `val-fn' returns the same value; it may elide calls to `val-fn' +on that basis. New feature: if `val-fn' returns `:no-value' as a second value, the result will contain no pair with the corresponding key.")) +;;; Prior to 1.4.0, this unconditionally called `val-fn' on the defaults of the +;;; two maps to produce the defaults of the result. This was a PITA in the +;;; common case in which the defaults were null. Now, it calls `val-fn' only +;;; if at least one of the defaults is nonnull. (defgeneric map-intersection (map1 map2 &optional val-fn) (:documentation "Returns a map containing all the keys that are in the domains of both @@ -314,8 +324,13 @@ will contain no pair with the corresponding key.")) `val-fn' on the value from `map1' and the value from `map2'. `val-fn' defaults to simply returning its second argument, so the entries in `map2' simply shadow those in `map1'. The default for the new map is the result -of calling `val-fn' on the defaults for the two maps (so be sure it can -take these values).")) +of calling `val-fn' on the defaults for the two maps, if either of those +is nonnull. `map-intersection' assumes that if the two values passed to +`val-fn' are equal, `val-fn' returns the same value; it may elide calls +to `val-fn' on that basis. + +New feature: if `val-fn' returns `:no-value' as a second value, the result +will contain no pair with the corresponding key.")) (defgeneric map-difference-2 (map1 map2) (:documentation @@ -2024,13 +2039,17 @@ symbols.")) &optional (val-fn (fn (_v1 v2) v2))) (make-wb-map (WB-Map-Tree-Union (wb-map-contents map1) (wb-map-contents map2) (coerce val-fn 'function)) - (funcall val-fn (map-default map1) (map-default map2)))) + (let ((def1 (map-default map1)) + (def2 (map-default map2))) + (and (or def1 def2) (funcall val-fn def1 def2))))) (defmethod map-intersection ((map1 wb-map) (map2 wb-map) &optional (val-fn (fn (_v1 v2) v2))) (make-wb-map (WB-Map-Tree-Intersect (wb-map-contents map1) (wb-map-contents map2) (coerce val-fn 'function)) - (funcall val-fn (map-default map1) (map-default map2)))) + (let ((def1 (map-default map1)) + (def2 (map-default map2))) + (and (or def1 def2) (funcall val-fn def1 def2))))) (defmethod map-difference-2 ((map1 wb-map) (map2 wb-map)) (let ((newc1 newc2 (WB-Map-Tree-Diff-2 (wb-map-contents map1) (wb-map-contents map2)))) diff --git a/Code/relations.lisp b/Code/relations.lisp index 391bd7a..ff247bf 100644 --- a/Code/relations.lisp +++ b/Code/relations.lisp @@ -218,14 +218,16 @@ constructed." (lambda (s1 s2) (let ((s (WB-Set-Tree-Intersect s1 s2))) (incf new-size (WB-Set-Tree-Size s)) - s)))) + (values s (and (null s) ':no-value)))))) (new-map1 (and (or (wb-2-relation-map1 br1) (wb-2-relation-map1 br2)) (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)))))) + (lambda (s1 s2) + (let ((s (WB-Set-Tree-Intersect s1 s2))) + (values s (and (null s) ':no-value)))))))))) (make-wb-2-relation new-size new-map0 new-map1))) (defgeneric join (relation-a column-a relation-b column-b) diff --git a/Code/testing.lisp b/Code/testing.lisp index f0894c0..7caedfb 100644 --- a/Code/testing.lisp +++ b/Code/testing.lisp @@ -94,8 +94,10 @@ (and (less-than? a b) (greater-than? b a))) (unequal? (a b) - (and (eq (compare a b) ':unequal) - (eq (compare b a) ':unequal)))) + (let ((ab (eq (compare a b) ':unequal)) + (ba (eq (compare b a) ':unequal))) + (assert (eq ab ba)) + ab))) (test (less-than? nil 1)) (test (less-than? 1 2)) (test (equal? 11/31 11/31)) @@ -794,6 +796,75 @@ (test (let ((ht (convert 'hash-table (map (1 2) (3 4)) :test 'equal))) (eql (hash-table-test ht) 'equal))) + (test (equal? (map-union (map (0 50) (1 88)) + (map (0 51) (1 97)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map (1 97)))) + (test (equal? (map-union (map (0 50) (1 88)) + (map (0 51) (2 104)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map (1 88) (2 104)))) + (test (equal? (map-union (map ((make-my-integer 0) 32) ((make-my-integer 1) 77)) + (map ((make-my-integer 0) 33) ((make-my-integer 1) 92)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 1) 92)))) + (test (equal? (map-union (map ((make-my-integer 0) 32) ((make-my-integer 1) 55)) + (map ((make-my-integer 0) 33) ((make-my-integer 2) 84)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 1) 55) ((make-my-integer 2) 84)))) + (test (equal? (map-union (map ((make-my-integer 0) 32) ((make-my-integer 1) 56)) + (map ((make-my-integer 0) 34) ((make-my-integer 2) 88)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 0) 34) ((make-my-integer 1) 56) ((make-my-integer 2) 88)))) + (test (equal? (map-union (map ((make-my-integer 0) 32) ((make-my-integer 2) 47)) + (map ((make-my-integer 0) 33) ((make-my-integer 1) 23)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 1) 23) ((make-my-integer 2) 47)))) + (test (equal? (map-union (map ((make-my-integer 0) 33) ((make-my-integer 2) 28)) + (map ((make-my-integer 0) 32) ((make-my-integer 1) 19)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 0) 32) ((make-my-integer 1) 19) ((make-my-integer 2) 28)))) + (test (equal? (map-union (map ((make-my-integer 0) 16) ((make-my-integer 2) 44)) + (map ((make-my-integer 0) 17) ((make-my-integer 2) 67)) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 2) 67)))) + (let ((m1 (gmap :map (fn (x) (values (make-my-integer (* x 2)) x)) (:index -8 8))) + (m2 (gmap :map (fn (x) (values (make-my-integer (* x 2)) (1+ x))) (:index -8 8)))) + (test (equal? (map-union m1 m2 + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map))) + (test (equal? (map-union m1 (with m2 (make-my-integer 0) 99) + (fn (x y) (if (equal? (1+ x) y) (values nil ':no-value) y))) + (map ((make-my-integer 0) 99))))) + + (test (equal? (map-intersection (map ((make-my-integer 0) 16) ((make-my-integer 1) 44)) + (map ((make-my-integer 0) 16) ((make-my-integer 1) 72)) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (map ((make-my-integer 0) 16)))) + (test (equal? (map-intersection (map ((make-my-integer 0) 17) ((make-my-integer 1) 44)) + (map ((make-my-integer 0) 17) ((make-my-integer 2) 72)) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (map ((make-my-integer 0) 17)))) + (test (equal? (map-intersection (map ((make-my-integer 0) 15) ((make-my-integer 1) 44)) + (map ((make-my-integer 0) 17) ((make-my-integer 2) 72)) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (map))) + (test (equal? (map-intersection (map ((make-my-integer 0) 17) ((make-my-integer 2) 71)) + (map ((make-my-integer 0) 17) ((make-my-integer 1) 44)) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (map ((make-my-integer 0) 17)))) + (test (equal? (map-intersection (map ((make-my-integer 0) 17) ((make-my-integer 2) 72)) + (map ((make-my-integer 0) 15) ((make-my-integer 1) 44)) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (map))) + (let ((m (gmap :map (fn (x) (values (make-my-integer (* x 2)) x)) (:index -8 8)))) + (test (equal? (map-intersection m (with m (make-my-integer 0) 99) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (less m (make-my-integer 0)))) + (test (equal? (map-intersection (with m (make-my-integer 0) 99) (with m (make-my-integer 0) 99) + (fn (x y) (if (equal? x y) x (values nil ':no-value)))) + (with m (make-my-integer 0) 99)))) + (test (equal (multiple-value-list (find 1 (map))) '(nil nil))) (test (equal (multiple-value-list (find 1 (map (1 3)))) '(1 3))) (test (equal (multiple-value-list (find 2 (map (2 3)))) '(2 3))) @@ -2321,10 +2392,10 @@ (tmp (with fm0 r v))) (setq m0 (Alist-Assign m0 r v)) (unless (verify tmp) - (error "Map verify failed on iteration ~D, adding ~A -> ~A; ~D, ~D" + (error "Map verify failed (a) on iteration ~D, adding ~A -> ~A; ~D, ~D" i r v m0 tmp)) (unless (= (size tmp) (length m0)) - (error "Map size or with failed on iteration ~D, adding ~A -> ~A; ~D, ~D" + (error "Map size or with failed (a) on iteration ~D, adding ~A -> ~A; ~D, ~D" i r v m0 tmp)) (setq fm0 tmp))) (dotimes (j 100) @@ -2333,10 +2404,10 @@ (tmp (with fm1 r v))) (setq m1 (Alist-Assign m1 r v)) (unless (verify tmp) - (error "Map verify failed on iteration ~D, adding ~A -> ~A; ~D, ~D" + (error "Map verify failed (a) on iteration ~D, adding ~A -> ~A; ~D, ~D" i r v m1 tmp)) (unless (= (size tmp) (length m1)) - (error "Map size or with failed on iteration ~D, adding ~A -> ~A; ~D, ~D" + (error "Map size or with failed (b) on iteration ~D, adding ~A -> ~A; ~D, ~D" i r v m1 tmp)) (setq fm1 tmp))) (dotimes (j 20) @@ -3322,22 +3393,21 @@ (%c '((1 . 2) (3 . 4))))) (test (equal? (union (%c '((1 . 2))) (let ((m (%c '((3 . 4))))) - (get-inverse m) - m)) - (%c '((1 . 2) (3 . 4))))) + (inverse m))) + (%c '((1 . 2) (4 . 3))))) ;; intersection - ;; Buggy! - #+(or) - (progn - (test (equal? (intersection (%e) (%e)) (%e))) - (test (equal? (intersection (%c '((1 . 2))) (%e)) (%e))) - (test (equal? (intersection (%e) (%c '((1 . 2)))) (%e))) - (test (equal? (intersection (%c '((3 . 4))) (%c '((1 . 2)))) (%e))) - (test (equal? (intersection (%c '((1 . 2))) (%c '((1 . 2)))) - (%c '((1 . 2))))) - (test (equal? (intersection (%c '((1 . 2))) (%c '((1 . 3)))) (%e))) - (test (equal? (intersection (%c '((2 . 1))) (%c '((3 . 1)))) (%e))))))) + (test (equal? (intersection (%e) (%e)) (%e))) + (test (equal? (intersection (%c '((1 . 2))) (%e)) (%e))) + (test (equal? (intersection (%e) (%c '((1 . 2)))) (%e))) + (test (equal? (intersection (%c '((3 . 4))) (%c '((1 . 2)))) (%e))) + (test (equal? (intersection (%c '((1 . 2))) (%c '((1 . 2)))) + (%c '((1 . 2))))) + (test (equal? (intersection (%c '((1 . 2))) (%c '((1 . 3)))) (%e))) + (test (equal? (intersection (%c '((2 . 1))) (%c '((3 . 1)))) (%e))) + (test (equal? (intersection (%c '((a . 7) (b . 12) (c . 17))) (%c '((b . 12) (c . 22)))) + (%c '((b . 12)))))))) + ;;; Internal. (defgeneric verify (coll)) diff --git a/Code/wb-trees.lisp b/Code/wb-trees.lisp index c9c196c..410f152 100644 --- a/Code/wb-trees.lisp +++ b/Code/wb-trees.lisp @@ -3685,8 +3685,8 @@ in `eqvs1' are equivalent to those in `eqvs2'." "Print function for `WB-Map-Tree-Node', q.v." (if (or (null *print-level*) (<= depth *print-level*)) (format stream "~<#map-node<~;~D, ~S -> ~S, ~ - ~_~{~:[~S~;~<#(~;~@{~{~S -> ~S~}~^ ~:_~:}~;)~:>~]~}, ~ - ~_~{~:[~S~;~<#(~;~@{~{~S -> ~S~}~^ ~:_~:}~;)~:>~]~}~;>~:>" + ~_~{~:[~S~;~<#(~;~@{~{~S -> ~S~}~^, ~:_~:}~;)~:>~]~}, ~ + ~_~{~:[~S~;~<#(~;~@{~{~S -> ~S~}~^, ~:_~:}~;)~:>~]~}~;>~:>" (list (WB-Map-Tree-Node-Size node) (WB-Map-Tree-Node-Key node) (WB-Map-Tree-Node-Value node) @@ -3886,19 +3886,16 @@ shadowing any previous pair with the same key." (cons (Vector-Insert (car tree) idx key) (Vector-Insert (cdr tree) idx value)) (Make-WB-Map-Tree-Node (if found? - (Equivalent-Map-Union (svref (car tree) idx) - (svref (cdr tree) idx) - key value) + (Equivalent-Map-With (svref (car tree) idx) (svref (cdr tree) idx) + key value) key) value (and (> idx 0) (cons (Vector-Subseq (car tree) 0 idx) (Vector-Subseq (cdr tree) 0 idx))) - (and (< right-start (length (the simple-vector - (car tree)))) + (and (< right-start (length (the simple-vector (car tree)))) (cons (Vector-Subseq (car tree) right-start) - (Vector-Subseq (cdr tree) - right-start)))))))) + (Vector-Subseq (cdr tree) right-start)))))))) (t (let ((node-key (WB-Map-Tree-Node-Key tree)) ((comp (compare key node-key)))) @@ -3906,24 +3903,20 @@ shadowing any previous pair with the same key." ((:equal :unequal) ;; Since we're probably updating the value anyway, we don't bother trying ;; to figure out whether we can reuse the node. - (Make-WB-Map-Tree-Node (Equivalent-Map-Union node-key - (WB-Map-Tree-Node-Value tree) - key value) + (Make-WB-Map-Tree-Node (Equivalent-Map-With node-key (WB-Map-Tree-Node-Value tree) key value) value (WB-Map-Tree-Node-Left tree) (WB-Map-Tree-Node-Right tree))) ((:less) (WB-Map-Tree-Build-Node (WB-Map-Tree-Node-Key tree) (WB-Map-Tree-Node-Value tree) - (WB-Map-Tree-With (WB-Map-Tree-Node-Left tree) - key value) + (WB-Map-Tree-With (WB-Map-Tree-Node-Left tree) key value) (WB-Map-Tree-Node-Right tree))) ((:greater) (WB-Map-Tree-Build-Node (WB-Map-Tree-Node-Key tree) (WB-Map-Tree-Node-Value tree) (WB-Map-Tree-Node-Left tree) - (WB-Map-Tree-With (WB-Map-Tree-Node-Right tree) - key value)))))))) + (WB-Map-Tree-With (WB-Map-Tree-Node-Right tree) key value)))))))) (defun Vector-Update (vec idx val) "Returns a new vector like `vec' but with `val' at `idx'." @@ -4061,10 +4054,10 @@ pair removed." (let ((key2 (WB-Map-Tree-Node-Key tree2)) (val2 (WB-Map-Tree-Node-Value tree2)) ((eqvk1? eqvk1 eqvv1 (WB-Map-Tree-Find-Equivalent tree1 key2)) - ((key val (if eqvk1? (Equivalent-Map-Union eqvk1 eqvv1 key2 val2 val-fn) - (values key2 val2)))))) - (WB-Map-Tree-Concat - key val + ((nonnull? key val (if eqvk1? (Equivalent-Map-Union eqvk1 eqvv1 key2 val2 val-fn) + (values t key2 val2)))))) + (WB-Map-Tree-Concat-Maybe + nonnull? key val (WB-Map-Tree-Union-Rng (WB-Map-Tree-Trim tree1 lo key2) (WB-Map-Tree-Trim (WB-Map-Tree-Node-Left tree2) lo key2) @@ -4077,10 +4070,10 @@ pair removed." (let ((key1 (WB-Map-Tree-Node-Key tree1)) (val1 (WB-Map-Tree-Node-Value tree1)) ((eqvk2? eqvk2 eqvv2 (WB-Map-Tree-Find-Equivalent tree2 key1)) - ((key val (if eqvk2? (Equivalent-Map-Union key1 val1 eqvk2 eqvv2 val-fn) - (values key1 val1)))))) - (WB-Map-Tree-Concat - key val + ((nonnull? key val (if eqvk2? (Equivalent-Map-Union key1 val1 eqvk2 eqvv2 val-fn) + (values t key1 val1)))))) + (WB-Map-Tree-Concat-Maybe + nonnull? key val (WB-Map-Tree-Union-Rng (WB-Map-Tree-Node-Left tree1) (WB-Map-Tree-Trim tree2 lo key1) val-fn lo key1) @@ -4713,9 +4706,10 @@ between equal trees." (vals nil) (any-equivalent? nil)) ((and (= i1 len1) (= i2 len2)) - (values (cons (coerce (nreverse keys) 'simple-vector) - (coerce (nreverse vals) 'simple-vector)) - any-equivalent?)) + ;; The use of `:no-value' could produce an empty result. + (and keys (values (cons (coerce (nreverse keys) 'simple-vector) + (coerce (nreverse vals) 'simple-vector)) + any-equivalent?))) (cond ((= i1 len1) (do () ((= i2 len2)) (push (svref keys2 i2) keys) @@ -4735,9 +4729,9 @@ between equal trees." (let ((new-val second-val (funcall val-fn (svref vals1 i1) (svref vals2 i2)))) (unless (eq second-val ':no-value) (push key1 keys) - (push new-val vals) - (incf i1) - (incf i2)))) + (push new-val vals))) + (incf i1) + (incf i2)) ((:less) (push key1 keys) (push (svref vals1 i1) vals) @@ -4747,13 +4741,16 @@ between equal trees." (push (svref vals2 i2) vals) (incf i2)) ((:unequal) - (push (Equivalent-Map-Union key1 (svref vals1 i1) - key2 (svref vals2 i2) val-fn) - keys) - (push nil vals) - (incf i1) - (incf i2) - (setq any-equivalent? t))))))))) + (let ((nonnull? key val + (Equivalent-Map-Union key1 (svref vals1 i1) + key2 (svref vals2 i2) val-fn))) + (when nonnull? + (push key keys) + (push val vals) + (when (Equivalent-Map? key) + (setq any-equivalent? t)))) + (incf i1) + (incf i2))))))))) (defun Vector-Pair-Intersect (pr1 pr2 val-fn lo hi) (declare (optimize (speed 3) (safety 0)) @@ -4784,10 +4781,12 @@ between equal trees." ((comp (compare key1 key2)))) (ecase comp ((:equal) - (push key1 keys) - (push (funcall val-fn (svref vals1 i1) (svref vals2 i2)) vals) - (incf i1) - (incf i2)) + (let ((new-val second-val (funcall val-fn (svref vals1 i1) (svref vals2 i2)))) + (unless (eq second-val ':no-value) + (push key1 keys) + (push new-val vals))) + (incf i1) + (incf i2)) ((:less) (incf i1)) ((:greater) @@ -5084,6 +5083,12 @@ between equal trees." ;;; ================================================================================ ;;; Equivalent-Map routines +(defun Equivalent-Map-With (key1 val1 key2 val2) + (let ((nonnull? key val (Equivalent-Map-Union key1 val1 key2 val2))) + (declare (ignore nonnull?)) + ;; `nonnull?' must be true since we used the default `val-fn'. + (values key val))) + (defun Equivalent-Map-Union (key1 val1 key2 val2 &optional (val-fn #'(lambda (v1 v2) (declare (ignore v1)) @@ -5113,25 +5118,43 @@ otherwise one value is returned, which is an `Equivalent-Map'." (let ((pr1 (find (car pr2) alist1 :test #'equal? :key #'car))) (when (null pr1) (push pr2 result)))) - (Make-Equivalent-Map result)) + (cond ((null result) nil) + ((cdr result) + (values t (Make-Equivalent-Map result))) + (t + (values t (caar result) (cdar result))))) (let ((alist1 (Equivalent-Map-Alist key1)) ((pr1 (find key2 alist1 :test #'equal? :key #'car)))) (declare (type list alist1)) - (when pr1 - (setq alist1 (remove pr1 alist1)) - (setq val2 (funcall val-fn (cdr pr1) val2))) - (Make-Equivalent-Map (cons (cons key2 val2) alist1)))) + (if pr1 + (progn + (setq alist1 (remove pr1 alist1)) + (let ((new-val second-val (funcall val-fn (cdr pr1) val2))) + (if (eq second-val ':no-value) + (if (null (cdr alist1)) + (values t (caar alist1) (cdar alist1)) + (values t (Make-Equivalent-Map alist1))) + (values t (Make-Equivalent-Map (cons (cons key2 new-val) alist1)))))) + (values t (Make-Equivalent-Map (cons (cons key2 val2) alist1)))))) (if (Equivalent-Map? key2) (let ((alist2 (Equivalent-Map-Alist key2)) ((pr2 (find key1 alist2 :test #'equal? :key #'car)))) (declare (type list alist2)) - (when pr2 - (setq alist2 (remove pr2 alist2)) - (setq val1 (funcall val-fn val1 (cdr pr2)))) - (Make-Equivalent-Map (cons (cons key1 val1) alist2))) + (if pr2 + (progn + (setq alist2 (remove pr2 alist2)) + (let ((new-val second-val (funcall val-fn val1 (cdr pr2)))) + (if (eq second-val ':no-value) + (if (null (cdr alist2)) + (values t (caar alist2) (cdar alist2)) + (values t (Make-Equivalent-Map alist2))) + (values t (Make-Equivalent-Map (cons (cons key1 new-val) alist2)))))) + (values t (Make-Equivalent-Map (cons (cons key1 val1) alist2))))) (if (equal? key1 key2) - (values key1 (funcall val-fn val1 val2)) - (Make-Equivalent-Map (list (cons key1 val1) (cons key2 val2))))))) + (let ((new-val second-val (funcall val-fn val1 val2))) + (and (not (eq second-val ':no-value)) + (values t key1 new-val))) + (values t (Make-Equivalent-Map (list (cons key1 val1) (cons key2 val2)))))))) (defun Equivalent-Map-Intersect (key1 val1 key2 val2 val-fn) "Both `key1' and `key2' may be single values (representing a single key/value @@ -5152,7 +5175,9 @@ is null, returns false." (dolist (pr1 alist1) (let ((pr2 (cl:find (car pr1) alist2 :test #'equal? :key #'car))) (when pr2 - (push (cons (car pr1) (funcall val-fn (cdr pr1) (cdr pr2))) result)))) + (let ((new-val second-val (funcall val-fn (cdr pr1) (cdr pr2)))) + (unless (eq second-val ':no-value) + (push (cons (car pr1) new-val) result)))))) (and result (if (cdr result) (values t (Make-Equivalent-Map result)) @@ -5161,15 +5186,18 @@ is null, returns false." ((pr1 (cl:find key2 alist1 :test #'equal? :key #'car)))) (declare (type list alist1)) (and pr1 - (values t key2 (funcall val-fn (cdr pr1) val2))))) + (let ((new-val second-val (funcall val-fn (cdr pr1) val2))) + (and (not (eq second-val ':no-value)) (values t key2 new-val)))))) (if (Equivalent-Map? key2) (let ((alist2 (Equivalent-Map-Alist key2)) ((pr2 (cl:find key1 alist2 :test #'equal? :key #'car)))) (declare (type list alist2)) (and pr2 - (values t key1 (funcall val-fn val1 (cdr pr2))))) + (let ((new-val second-val (funcall val-fn val1 (cdr pr2)))) + (and (not (eq second-val ':no-value)) (values t key1 new-val))))) (and (equal? key1 key2) - (values t key1 (funcall val-fn val1 val2)))))) + (let ((new-val second-val (funcall val-fn val1 val2))) + (and (not (eq second-val ':no-value)) (values t key1 new-val))))))) (defun Equivalent-Map-Difference (key1 val1 key2 val2) "Both `key1' and `key2' may be single values (representing a single key/value