Raymond Toy pushed to branch issue-240-add-hashtable-set-exclusive-or at cmucl / cmucl
Commits:
-
87e54e03
by Raymond Toy at 2023-08-25T14:15:52+00:00
-
4542d55e
by Raymond Toy at 2023-08-25T14:16:04+00:00
-
d1bf9185
by Raymond Toy at 2023-08-25T08:34:43-07:00
2 changed files:
Changes:
... | ... | @@ -749,7 +749,9 @@ |
749 | 749 | (defparameter *min-list-length-for-hashtable*
|
750 | 750 | 15)
|
751 | 751 | |
752 | -(declaim (start-block list-to-hashtable union intersection set-difference))
|
|
752 | +(declaim (start-block list-to-hashtable
|
|
753 | + union intersection set-difference
|
|
754 | + nunion nintersection nset-difference))
|
|
753 | 755 | |
754 | 756 | ;; Main code to process a set function. INIT-RES initializes the
|
755 | 757 | ;; value of RES, which holds the result of the set function.
|
... | ... | @@ -836,8 +838,6 @@ |
836 | 838 | (process-set nil t))
|
837 | 839 | |
838 | 840 | |
839 | -(declaim (end-block))
|
|
840 | - |
|
841 | 841 | ;;; Destination and source are setf-able and many-evaluable. Sets the source
|
842 | 842 | ;;; to the cdr, and "conses" the 1st elt of source to destination.
|
843 | 843 | ;;;
|
... | ... | @@ -847,19 +847,42 @@ |
847 | 847 | (cdr temp) ,destination
|
848 | 848 | ,destination temp)))
|
849 | 849 | |
850 | +;;; Main body for destructive set operations. INIT-RES initializes
|
|
851 | +;;; the result list. INVERT-P is T if the result of the TEST-FORM
|
|
852 | +;;; should be inverted. TEST-FORM is the form used for determining
|
|
853 | +;;; how to update the result.
|
|
854 | +(defmacro nprocess-set-body (init-res invert-p test-form)
|
|
855 | + `(let ((res ,init-res)
|
|
856 | + (list1 list1))
|
|
857 | + (do ()
|
|
858 | + ((endp list1))
|
|
859 | + (if ,(if invert-p
|
|
860 | + `(not ,test-form)
|
|
861 | + test-form)
|
|
862 | + (steve-splice list1 res)
|
|
863 | + (setq list1 (cdr list1))))
|
|
864 | + res))
|
|
865 | + |
|
866 | +;; Implementation of the destructive set operations. INIT-RES
|
|
867 | +;; initializes the value of the result list. INVERT-P indicates
|
|
868 | +;; whether to invert the test-form used to determine how the result
|
|
869 | +;; should be updated.
|
|
870 | +(defmacro nprocess-set (init-res invert-p)
|
|
871 | + `(let ((hashtable (list-to-hashtable list2 key test test-not)))
|
|
872 | + (if hashtable
|
|
873 | + (nprocess-set-body ,init-res ,invert-p
|
|
874 | + (nth-value 1 (gethash (apply-key key (car list1)) hashtable)))
|
|
875 | + (nprocess-set-body ,init-res ,invert-p
|
|
876 | + (with-set-keys (member (apply-key key (car list1)) list2))))))
|
|
877 | + |
|
878 | + |
|
850 | 879 | (defun nunion (list1 list2 &key key (test #'eql testp) (test-not nil notp))
|
851 | 880 | "Destructively returns the union list1 and list2."
|
852 | 881 | (declare (inline member))
|
853 | 882 | (if (and testp notp)
|
854 | 883 | (error "Test and test-not both supplied."))
|
855 | - (let ((res list2)
|
|
856 | - (list1 list1))
|
|
857 | - (do ()
|
|
858 | - ((endp list1))
|
|
859 | - (if (not (with-set-keys (member (apply-key key (car list1)) list2)))
|
|
860 | - (steve-splice list1 res)
|
|
861 | - (setf list1 (cdr list1))))
|
|
862 | - res))
|
|
884 | + |
|
885 | + (nprocess-set list2 t))
|
|
863 | 886 |
|
864 | 887 | (defun nintersection (list1 list2 &key key
|
865 | 888 | (test #'eql testp) (test-not nil notp))
|
... | ... | @@ -867,13 +890,8 @@ |
867 | 890 | (declare (inline member))
|
868 | 891 | (if (and testp notp)
|
869 | 892 | (error "Test and test-not both supplied."))
|
870 | - (let ((res nil)
|
|
871 | - (list1 list1))
|
|
872 | - (do () ((endp list1))
|
|
873 | - (if (with-set-keys (member (apply-key key (car list1)) list2))
|
|
874 | - (steve-splice list1 res)
|
|
875 | - (setq list1 (Cdr list1))))
|
|
876 | - res))
|
|
893 | + |
|
894 | + (nprocess-set nil nil))
|
|
877 | 895 | |
878 | 896 | (defun nset-difference (list1 list2 &key key
|
879 | 897 | (test #'eql testp) (test-not nil notp))
|
... | ... | @@ -881,14 +899,10 @@ |
881 | 899 | (declare (inline member))
|
882 | 900 | (if (and testp notp)
|
883 | 901 | (error "Test and test-not both supplied."))
|
884 | - (let ((res nil)
|
|
885 | - (list1 list1))
|
|
886 | - (do () ((endp list1))
|
|
887 | - (if (not (with-set-keys (member (apply-key key (car list1)) list2)))
|
|
888 | - (steve-splice list1 res)
|
|
889 | - (setq list1 (cdr list1))))
|
|
890 | - res))
|
|
891 | 902 | |
903 | + (nprocess-set nil t))
|
|
904 | + |
|
905 | +(declaim (end-block))
|
|
892 | 906 | |
893 | 907 | (defun set-exclusive-or (list1 list2 &key key
|
894 | 908 | (test #'eql testp) (test-not nil notp))
|
... | ... | @@ -88,8 +88,37 @@ |
88 | 88 | :test 'eql
|
89 | 89 | :test-not 'eql)))
|
90 | 90 | |
91 | -
|
|
92 | 91 | |
92 | +(define-test nset-diff.1
|
|
93 | + (:tag :issues)
|
|
94 | + ;; From CLHS
|
|
95 | + (flet
|
|
96 | + ((test1 (min-length-limit)
|
|
97 | + (let ((lisp::*min-list-length-for-hashtable* min-length-limit)
|
|
98 | + (lst1 (list "A" "b" "C" "d"))
|
|
99 | + (lst2 (list "a" "B" "C" "d")))
|
|
100 | + (assert-equal '("b" "A")
|
|
101 | + (nset-difference lst1 lst2 :test 'equal))
|
|
102 | + ;; This isn't specified by the CLHS, but it is what we do.
|
|
103 | + (assert-equal '("A") lst1))))
|
|
104 | + (test1 100)
|
|
105 | + (test1 1)))
|
|
106 | + |
|
107 | +(define-test nset-diff.key
|
|
108 | + (:tag :issues)
|
|
109 | + (flet
|
|
110 | + ((test (min-length-limit)
|
|
111 | + ;; From CLHS
|
|
112 | + (let ((lisp::*min-list-length-for-hashtable* min-length-limit)
|
|
113 | + (lst1 (list '("a" . "b") '("c" . "d") '("e" . "f")))
|
|
114 | + (lst2 (list '("c" . "a") '("e" . "b") '("d" . "a"))))
|
|
115 | + (assert-equal '(("e" . "f") ("c" . "d"))
|
|
116 | + (nset-difference lst1 lst2 :test 'equal :key #'cdr))
|
|
117 | + ;; This isn't specified by the CLHS, but it is what we do.
|
|
118 | + (assert-equal '(("a" . "b") ("c" . "d")) lst1))))
|
|
119 | + (test 100)
|
|
120 | + (test 1)))
|
|
121 | +
|
|
93 | 122 | (define-test union.hash-eql
|
94 | 123 | (:tag :issues)
|
95 | 124 | ;; For union to use hashtables by making the threshold
|
... | ... | @@ -173,6 +202,33 @@ |
173 | 202 | :test 'eql
|
174 | 203 | :test-not 'eql)))
|
175 | 204 | |
205 | +(define-test nunion.1
|
|
206 | + (:tag :issues)
|
|
207 | + (flet
|
|
208 | + ((test (min-list-length)
|
|
209 | + (let ((lisp::*min-list-length-for-hashtable* min-list-length)
|
|
210 | + (lst1 (list 1 2 '(1 2) "a" "b"))
|
|
211 | + (lst2 (list 2 3 '(2 3) "B" "C")))
|
|
212 | + (assert-equal '("b" "a" (1 2) 1 2 3 (2 3) "B" "C")
|
|
213 | + (nunion lst1 lst2))
|
|
214 | + (assert-equal '(1 2 3 (2 3) "B" "C")
|
|
215 | + lst1))))
|
|
216 | + (test 100)
|
|
217 | + (test 1)))
|
|
218 | + |
|
219 | +(define-test nintersection.1
|
|
220 | + (:tag :issues)
|
|
221 | + (flet
|
|
222 | + ((test (min-list-length)
|
|
223 | + (let ((lisp::*min-list-length-for-hashtable* min-list-length)
|
|
224 | + (lst1 (list 1 1 2 3 4 'a 'b 'c "A" "B" "C" "d"))
|
|
225 | + (lst2 (list 1 4 5 'b 'c 'd "a" "B" "c" "D")))
|
|
226 | + (assert-equal '(c b 4 1 1)
|
|
227 | + (nintersection lst1 lst2))
|
|
228 | + (assert-equal '(1) lst1))))
|
|
229 | + (test 100)
|
|
230 | + (test 1)))
|
|
231 | + |
|
176 | 232 | |
177 | 233 | (define-test subsetp.hash-eq
|
178 | 234 | (:tag :issues)
|