;;; -*- Mode:LISP; Base:10; Readtable:CL -*- ;;; fast set operations using bignum arithmetic: 11/23/85 17:32:23 -George Carrette ;;; Rewritten for use in micro-tracer by JRM. ;;; unfortunately common-lisp already has a function named SET-DIFFERENCE, so ;;; we call ours SET-DIFF. ditto for SET-EXCLUSIVE-OR. ;;; Universes (universes?) (defvar *default-universe-size* 100. "Default size for all universes.") (defstruct (universe (:callable-constructors nil) (:constructor internal-make-universe) (:print-function print-universe)) element->number-map number->element-map) (defun print-universe (universe stream ignore) (format-unreadable-object stream #'(lambda () (format stream "Universe ~D with ~D element~:P" (%pointer universe) (length (universe-number->element-map universe)))))) (defun make-universe (&optional (size *default-universe-size*)) (internal-make-universe :element->number-map (make-hash-table :test 'equal :size size) :number->element-map (make-array size :adjustable t :fill-pointer 0))) (defun universe-size (universe) (length (universe-number->element-map universe))) (defun element->number (element universe) (gethash element (universe-element->number-map universe))) (defun number->element (number universe) (aref (universe-number->element-map universe) number)) (defun element->number-inserting (element universe) (or (element->number element universe) (prog1 (puthash element (length (universe-number->element-map universe)) (universe-element->number-map universe)) (vector-push-extend element (universe-number->element-map universe))))) (defvar *default-universe* (make-universe)) ;;; Sets (defstruct (set (:callable-constructors nil) (:constructor internal-make-set) (:print-function set-printing-function)) universe contents) (defsynonym set? set-p) (defsubst element-bit-is-on? (index contents) (logbitp index contents)) (defun spread-set (set receiver) "(FUNCALL (SET-UNIVERSE ) (SET-CONTENTS ))" (funcall receiver (set-universe set) (set-contents set))) (defun spread-set-operation->set-operation (function) #'(lambda (set) (check-type set set) (spread-set set function))) (defun spread-set-significant-size (universe contents) ;; the set-contents will only be negative if we are using indefinite sets ;; which is not yet implemented. (if (minus? contents) (universe-size universe) (integer-length contents))) (defun spread-set-for-set-elements (universe contents function) (dotimes (index (spread-set-significant-size universe contents)) (when (element-bit-is-on? index contents) (funcall function (number->element index universe))))) (defun set-printing-function (set stream ignore) (format-unreadable-object stream #'(lambda () (spread-set set #'(lambda (universe contents) (if (zero? contents) (format stream "An Empty Set") (format stream "SET containing:") (spread-set-for-set-elements universe contents #'(lambda (element) (format stream " ~S" element))))))))) (defun set-alike? (set1 set2) (spread-set set1 #'(lambda (universe1 contents1) (spread-set set2 #'(lambda (universe2 contents2) (and (eq? universe1 universe2) (= contents1 contents2))))))) (defalike #'set? #'set-alike?) (defun set-adjoin-element! (set element) (spread-set set #'(lambda (universe contents) (setf (set-contents set) (dpb 1 (byte 1 (element->number-inserting element universe)) contents)))) set) (deff set-adjoin-elements! (binary-function->left-associating-reducer #'set-adjoin-element! #'identity)) (defsubst set-push! (element set) (set-adjoin-element! set element)) (defun make-empty-set (&optional (universe *default-universe*)) (internal-make-set :universe universe :contents 0)) (defun empty-set? (object) (and (set? object) (zero? (set-contents object)))) (defun elements->set-contents (element-list universe) (let ((contents-bignum 0)) (dolist (element element-list) (setf (ldb (byte 1 (element->number-inserting element universe)) contents-bignum) 1)) contents-bignum)) (defun list->set (element-list &optional (universe *default-universe*)) (internal-make-set :universe universe :contents (elements->set-contents element-list universe))) (defun make-set (&rest elements) (list->set (copylist elements))) (defun for-set-elements (set function) (spread-set set #'(lambda (universe contents) (spread-set-for-set-elements universe contents function)))) (defun for-single-element-subsets (set function) (spread-set set #'(lambda (universe contents) (dotimes (index (spread-set-significant-size universe contents)) (when (element-bit-is-on? index contents) (funcall function (internal-make-set :universe universe :contents (^ 2 index)))))))) (defun set-filter (set predicate) (let ((result-set (make-empty-set))) (for-single-element-subsets set #'(lambda (subset) (when (funcall predicate subset) (setq result-set (set-union result-set subset))))) result-set)) (defun set->list-of-subsets (set) (let ((subset-list '())) (for-single-element-subsets set #'(lambda (subset) (push subset subset-list))) subset-list)) (defun set-element? (element set) (check-type set set) (spread-set set #'(lambda (universe contents) (let ((index (element->number element universe))) (and index (element-bit-is-on? index contents)))))) (defun spread-set-set->list (universe contents) (let ((elements '())) (spread-set-for-set-elements universe contents #'(lambda (element) (push element elements))) elements)) (deff set->list (spread-set-operation->set-operation #'spread-set-set->list)) ;; cant use LOGNOT here because that would cause a set to GROW as items were added to ;; the universe. Experiment with that (indefinite sets) later. (defun spread-set-complement (universe contents) (internal-make-set :universe universe :contents (logandc2 (1- (^ 2 (universe-size universe))) contents))) (deff set-complement (spread-set-operation->set-operation #'spread-set-complement)) (defun spread-set-cardinality (ignore-universe contents) ignore-universe (logcount contents)) (deff set-cardinality (spread-set-operation->set-operation #'spread-set-cardinality)) (defun check-set-universes (universe1 universe2) (when (not (eq? universe1 universe2)) (cerror "Do it anyway (result is meaningless)." "Operation on sets from different universes: ~S and ~S" universe1 universe2))) (defun logical-operation->set-operation (logop) #'(lambda (set1 set2) (check-type set1 set) (check-type set2 set) (spread-set set1 #'(lambda (universe1 contents1) (spread-set set2 #'(lambda (universe2 contents2) (check-set-universes universe1 universe2) (internal-make-set :universe universe1 ;or universe2, they are eq :contents (funcall logop contents1 contents2)))))))) (defmacro define-set-operation (name contents-operation) `(defun ,name (&rest sets) (left-associating-reduce (logical-operation->set-operation (function ,contents-operation)) (make-empty-set) sets))) (define-set-operation set-union logior) (define-set-operation set-xor logxor) (deff set-intersection (binary-function->left-associating-reducer (logical-operation->set-operation #'logand) #'identity)) ;; fix bug in system. Documentation of boole is wrong and so are some of the constants. (setq boole-andc2 4) (deff set-diff (binary-function->left-associating-reducer (logical-operation->set-operation #'logandc2)))