Raymond Toy pushed to branch master at cmucl / cmucl

Commits:

2 changed files:

Changes:

  • src/code/list.lisp
    ... ... @@ -45,7 +45,7 @@
    45 45
     	  tree-equal list-length nth %setnth nthcdr last make-list append
    
    46 46
     	  copy-list copy-alist copy-tree revappend nconc nreconc butlast
    
    47 47
     	  nbutlast ldiff member member-if member-if-not tailp adjoin union
    
    48
    -	  nunion intersection nintersection set-difference nset-difference
    
    48
    +	  nunion intersection nintersection nset-difference
    
    49 49
     	  set-exclusive-or nset-exclusive-or subsetp acons pairlis assoc
    
    50 50
     	  assoc-if assoc-if-not rassoc rassoc-if rassoc-if-not subst subst-if
    
    51 51
     	  subst-if-not nsubst nsubst-if nsubst-if-not sublis nsublis))
    
    ... ... @@ -744,6 +744,39 @@
    744 744
           list
    
    745 745
           (cons item list)))
    
    746 746
     
    
    747
    +;; The minimum length of a list before we can use a hashtable.  This
    
    748
    +;; was determined experimentally.
    
    749
    +(defparameter *min-list-length-for-hashtable*
    
    750
    +  15)
    
    751
    +
    
    752
    +;; Convert a list to a hashtable.  The hashtable does not handle
    
    753
    +;; duplicated values in the list.  Returns the hashtable.
    
    754
    +(defun list-to-hashtable (list key test test-not)
    
    755
    +  ;; Don't currently support test-not when converting a list to a hashtable
    
    756
    +  (unless test-not
    
    757
    +    (let ((hash-test (let ((test-fn (if (and (symbolp test)
    
    758
    +                                             (fboundp test))
    
    759
    +                                        (fdefinition test)
    
    760
    +                                        test)))
    
    761
    +                       (cond ((eql test-fn #'eq) 'eq)
    
    762
    +                             ((eql test-fn #'eql) 'eql)
    
    763
    +                             ((eql test-fn #'equal) 'equal)
    
    764
    +                             ((eql test-fn #'equalp) 'equalp)))))
    
    765
    +      (unless hash-test
    
    766
    +	(return-from list-to-hashtable nil))
    
    767
    +      ;; If the list is too short, the hashtable makes things
    
    768
    +      ;; slower.  We also need to balance memory usage.
    
    769
    +      (let ((len 0))
    
    770
    +	;; Compute list length ourselves.
    
    771
    +	(dolist (item list)
    
    772
    +	  (declare (ignore item))
    
    773
    +	  (incf len))
    
    774
    +	(when (< len *min-list-length-for-hashtable*)
    
    775
    +          (return-from list-to-hashtable nil))
    
    776
    +	(let ((hashtable (make-hash-table :test hash-test :size len)))
    
    777
    +	  (dolist (item list)
    
    778
    +	    (setf (gethash (apply-key key item) hashtable) item))
    
    779
    +	  hashtable)))))
    
    747 780
     
    
    748 781
     ;;; UNION -- Public.
    
    749 782
     ;;;
    
    ... ... @@ -812,20 +845,32 @@
    812 845
     	  (setq list1 (Cdr list1))))
    
    813 846
         res))
    
    814 847
     
    
    815
    -(defun set-difference (list1 list2 &key key
    
    816
    -			     (test #'eql testp) (test-not nil notp))
    
    848
    +(defun set-difference (list1 list2 &key key (test #'eql testp) (test-not nil notp))
    
    817 849
       "Returns the elements of list1 which are not in list2."
    
    818 850
       (declare (inline member))
    
    819 851
       (if (and testp notp)
    
    820 852
           (error "Test and test-not both supplied."))
    
    821
    -  (if (null list2)
    
    822
    -      list1
    
    823
    -      (let ((res nil))
    
    824
    -	(dolist (elt list1)
    
    825
    -	  (if (not (with-set-keys (member (apply-key key elt) list2)))
    
    826
    -	      (push elt res)))
    
    827
    -	res)))
    
    828
    -
    
    853
    +  ;; Quick exit
    
    854
    +  (when (null list2)
    
    855
    +    (return-from set-difference list1))
    
    856
    +
    
    857
    +  (let ((hashtable 
    
    858
    +	  (list-to-hashtable list2 key test test-not)))
    
    859
    +    (cond (hashtable
    
    860
    +	   ;; list2 was placed in hash table.
    
    861
    +	   (let ((res nil))
    
    862
    +	     (dolist (item list1)
    
    863
    +	       (unless (nth-value 1 (gethash (apply-key key item) hashtable))
    
    864
    +		 (push item res)))
    
    865
    +	     res))
    
    866
    +	  ((null hashtable)
    
    867
    +	   ;; Default implementation because we didn't create the hash
    
    868
    +	   ;; table.
    
    869
    +           (let ((res nil))
    
    870
    +	     (dolist (item list1)
    
    871
    +	       (if (not (with-set-keys (member (apply-key key item) list2)))
    
    872
    +                   (push item res)))
    
    873
    +	     res)))))
    
    829 874
     
    
    830 875
     (defun nset-difference (list1 list2 &key key
    
    831 876
     			      (test #'eql testp) (test-not nil notp))
    

  • tests/sets.lisp
    1
    +;; Tests for sets
    
    2
    +
    
    3
    +(defpackage :sets-tests
    
    4
    +  (:use :cl :lisp-unit))
    
    5
    +
    
    6
    +(in-package "SETS-TESTS")
    
    7
    +
    
    8
    +(define-test set-diff.hash-eql
    
    9
    +    (:tag :issues)
    
    10
    +  ;; For set-difference to use hashtables by making the threshold
    
    11
    +  ;; small.
    
    12
    +  (let ((lisp::*min-list-length-for-hashtable* 2))
    
    13
    +    (assert-equal '(2 2 1)
    
    14
    +		  (set-difference '(1 2 2 3) '(3 4)))
    
    15
    +    (assert-equal '(2 2 1)
    
    16
    +		  (set-difference '(1 2 2 3) '(3 4 5 6 7 8)))
    
    17
    +    (assert-equal '(2 2 1)
    
    18
    +		  (set-difference '(1 2 2 3) '(3 4)
    
    19
    +				  :test #'eql))
    
    20
    +    (assert-equal '(2 2 1)
    
    21
    +		  (set-difference '(1 2 2 3) '(3 4 5 6 7 8)
    
    22
    +				  :test #'eql))))
    
    23
    +
    
    24
    +(define-test set-diff.hash-eq
    
    25
    +    (:tag :issues)
    
    26
    +  (let ((lisp::*min-list-length-for-hashtable* 2))
    
    27
    +    (assert-equal '(b b a)
    
    28
    +		  (set-difference '(a b b c) '(c d e) :test 'eq))
    
    29
    +    (assert-equal '(b b a)
    
    30
    +		  (set-difference '(a b b c) '(c d e f g h) :test 'eq))
    
    31
    +    (assert-equal '(b b a)
    
    32
    +		  (set-difference '(a b b c) '(c d e) :test #'eq))
    
    33
    +    (assert-equal '(b b a)
    
    34
    +		  (set-difference '(a b b c) '(c d e f g h) :test #'eq))))
    
    35
    +
    
    36
    +(define-test set-diff.hash-equal
    
    37
    +    (:tag :issues)
    
    38
    +  (let ((lisp::*min-list-length-for-hashtable* 2))
    
    39
    +    (assert-equal '("b" "b" "a")
    
    40
    +		  (set-difference '("a" "b" "b" "c")
    
    41
    +				  '("c" "d" "e")
    
    42
    +				  :test 'equal))
    
    43
    +    (assert-equal '("b" "b" "a")
    
    44
    +		  (set-difference '("a" "b" "b" "c")
    
    45
    +				  '("c" "d" "e" "f" "g" "h")
    
    46
    +				  :test 'equal))
    
    47
    +    (assert-equal '("b" "b" "a")
    
    48
    +		  (set-difference '("a" "b" "b" "c")
    
    49
    +				  '("c" "d" "e")
    
    50
    +				  :test #'equal))
    
    51
    +    (assert-equal '("b" "b" "a")
    
    52
    +		  (set-difference '("a" "b" "b" "c")
    
    53
    +				  '("c" "d" "e" "f" "g" "h")
    
    54
    +				  :test #'equal))))
    
    55
    +
    
    56
    +(define-test set-diff.hash-equalp
    
    57
    +    (:tag :issues)
    
    58
    +  (let ((lisp::*min-list-length-for-hashtable* 2))
    
    59
    +    (assert-equal '("b" "b" "a")
    
    60
    +		  (set-difference '("a" "b" "b" "c")
    
    61
    +				  '("C" "d" "e")
    
    62
    +				  :test 'equalp))
    
    63
    +    (assert-equal '("b" "b" "a")
    
    64
    +		  (set-difference '("a" "b" "b" "C")
    
    65
    +				  '("c" "D" "e" "f" "g" "h")
    
    66
    +				  :test 'equalp))
    
    67
    +    (assert-equal '("b" "b" "a")
    
    68
    +		  (set-difference '("a" "b" "b" "c")
    
    69
    +				  '("C" "d" "e")
    
    70
    +				  :test #'equalp))
    
    71
    +    (assert-equal '("b" "b" "a")
    
    72
    +		  (set-difference '("a" "b" "b" "C")
    
    73
    +				  '("c" "D" "e" "f" "g" "h")
    
    74
    +				  :test #'equalp))))
    
    75
    +
    
    76
    +;; Simple test that we handle a key correctly
    
    77
    +(define-test set-diff.hash-eql-with-key
    
    78
    +  (let ((lisp::*min-list-length-for-hashtable* 2))
    
    79
    +    (assert-equal '((3 "b") (2 "b"))
    
    80
    +		  (set-difference '((1 "a") (2 "b") (3 "b"))
    
    81
    +				  '((1 "a") (4 "c") (5 "d"))
    
    82
    +				  :key #'first))))
    
    83
    +
    
    84
    +(define-test set-diff.test-and-test-not
    
    85
    +  (assert-error 'simple-error
    
    86
    +		(set-difference '(1 2)
    
    87
    +				'(3 4)
    
    88
    +				:test 'eql
    
    89
    +				:test-not 'eql)))
    
    90
    +
    
    91
    +