branch: externals/hyperbole
commit 7853de7dec0b3e7f11f60ce48fd06ab8b6c56967
Author: Bob Weiner <[email protected]>
Commit: Bob Weiner <[email protected]>

    Add Mats set-tests.el with updates and update set.el
---
 ChangeLog         |   9 ++++
 set.el            | 107 +++++++++++++++++++++++++---------------
 test/MANIFEST     |   1 +
 test/set-tests.el | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 222 insertions(+), 38 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 0e2b9a7484..7aa6884bd4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2023-02-06  Bob Weiner  <[email protected]>
+
+* set.el (set:member): Optimize for common equality operators.
+         (set:add, set:remove): Convert from a macro to a function and 
optimize.
+        (set:replace): Rename to 'set:replace-key-value' and use 'set:replace'
+    to replace a non-keyed old-value with a new value.
+         (set:members): Keep members in stable order; previously returned in
+    reverse order.
+
 2023-02-05  Bob Weiner  <[email protected]>
 
 * hyrolo.el (hyrolo-initialize-file-list): Fix to not overwrite any user 
customized
diff --git a/set.el b/set.el
index e971fc769c..aa9962cb4d 100644
--- a/set.el
+++ b/set.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    26-Sep-91 at 19:24:19
-;; Last-Mod:      6-Aug-22 at 23:23:08 by Mats Lidell
+;; Last-Mod:      6-Feb-23 at 01:57:00 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -23,6 +23,7 @@
 ;;   a new set with the single member, 'element.
 
 ;;; Code:
+
 ;; ************************************************************************
 ;; Public variables
 ;; ************************************************************************
@@ -33,42 +34,54 @@ It must be a function of two arguments which returns 
non-nil only when
 the arguments are equivalent.")
 
 ;; ************************************************************************
-;; Public macros
+;; Public functions
 ;; ************************************************************************
 
+(defun set:add (elt set)
+  "Add element ELT to SET and then return SET.
+Uses `set:equal-op' for comparison.
+Use (setq set (set:add elt set)) to assure set is always properly modified."
+  (cond ((set:member elt set) set)
+        ((listp set) (setq set (cons elt set)))
+        (t (list elt))))
+
 (defun set:member (elt set)
   "Return non-nil if ELT is an element of SET.
 The value is actually the tail of SET whose car is ELT.
 Uses `set:equal-op' for comparison."
-  (while (and set (not (funcall set:equal-op elt (car set))))
-    (setq set (cdr set)))
-  set)
-
-(defmacro set:add (elt set)
-  "Add element ELT to SET and then return SET, even if SET is nil.
-Uses `set:equal-op' for comparison.
-Use (setq set (set:add elt set)) to assure set is always properly modified."
-  `(cond ((set:member ,elt ,set) ,set)
-        (,set (setq ,set (cons ,elt ,set)))
-        (t (list ,elt))))
-
-(defmacro set:remove (elt set)
+  (pcase set:equal-op
+    ('equal (member elt set))
+    ('eq    (memq elt set))
+    ('eql   (memql elt set))
+    (_      (while (and set (not (funcall set:equal-op elt (car set))))
+             (setq set (cdr set)))
+           set)))
+
+(defun set:remove (elt set)
   "Remove element ELT from SET and return new set.
 Assume SET is a valid set.  Uses `set:equal-op' for comparison.
 Use (setq set (set:remove elt set)) to assure set is always properly modified."
-  `(let ((rest (set:member ,elt ,set))
-        (rtn ,set))
-     (if rest
-        (cond ((= (length rtn) 1) (setq rtn nil))
-              ((= (length rest) 1)
-               (setcdr (nthcdr (- (length rtn) 2) rtn) nil))
-              (t (setcar rest (car (cdr rest)))
-                 (setcdr rest (cdr (cdr rest))))))
-     rtn))
+  (pcase set:equal-op
+    ('equal (delete elt set))
+    ((or 'eq 'eql) (delq elt set))
+    (_  (let ((rest (set:member elt set))
+             (rtn set))
+         (when rest
+           (cond ((= (length rtn) 1) (setq rtn nil))
+                 ((= (length rest) 1)
+                  (setcdr (nthcdr (- (length rtn) 2) rtn) nil))
+                 (t (setcar rest (cadr rest))
+                    (setcdr rest (cddr rest)))))
+         rtn))))
+
+(defun set:remove-key-value (key set)
+  "Remove element whose car matches KEY in SET.
+Return the set.  Use (setq set (set:remove-key-value key set)) to assure set is
+always properly modified. 
 
-;; ************************************************************************
-;; Public functions
-;; ************************************************************************
+Use `set:equal-op' to match against KEY.  Assume each element in the set has a
+car and a cdr."
+  (assoc-delete-all key set set:equal-op))
 
 (defun set:combinations (set &optional arity)
   "Return all possible combinations (subsets) of SET.
@@ -88,7 +101,8 @@ members."
                        (setq rest (nthcdr ctr set)
                              ctr (1+ ctr))
                        (mapcar (lambda (elt)
-                                 (if (listp elt) (cons first elt)
+                                 (if (listp elt)
+                                     (cons first elt)
                                    (list first elt)))
                                (set:combinations rest (1- arity))))
                      set))))))
@@ -98,10 +112,13 @@ members."
   "Return a new set created from any number of ELEMENTS.
 If no ELEMENTS are given, return the empty set.  Uses `set:equal-op'
 for comparison."
-  (let ((set))
-    (mapc (lambda (elt) (or (set:member elt set) (setq set (cons elt set))))
-         elements)
-    (nreverse set)))
+  (pcase set:equal-op
+    ('equal (delete-dups elements))
+    (_ (let ((set))
+        (mapc (lambda (elt) (unless (set:member elt set)
+                              (setq set (cons elt set))))
+              elements)
+        (nreverse set)))))
 
 (defalias 'set:delete 'set:remove)
 (defun set:difference (&rest sets)
@@ -113,7 +130,7 @@ other sets.  Uses `set:equal-op' for comparison."
            (mapc (lambda (elem) (setq rtn-set (set:remove elem rtn-set)))
                  set))
      (cdr sets))
-    (nreverse rtn-set)))
+    rtn-set))
 
 (defalias 'set:size 'length)
 
@@ -161,12 +178,26 @@ Uses `set:equal-op' for comparison.  See also 
`set:create'."
   (let ((set))
     (mapc (lambda (elt) (or (set:member elt set) (setq set (cons elt set))))
          list)
-    set))
+    (nreverse set)))
+
+(defun set:replace (old-elt new-elt set)
+  "Replace OLD-ELT with NEW-ELT in SET.  Add NEW-ELT if OLD-ELT is not in SET.
+Return the set.  Use (setq set (set:replace elt set)) to assure set is
+always properly modified. 
+
+Use `set:equal-op' for element comparisons."
+  (let ((elt-set (set:member old-elt set)))
+    (if elt-set
+       ;; replace element
+       (progn (setcar elt-set new-elt)
+              set)
+      ;; add new element
+      (cons new-elt set))))
 
-(defun set:replace (key value set)
-  "Replace or add element whose car matches KEY with element (KEY . VALUE) in 
SET.
-Return set if modified, else nil.
-Use (setq set (set:replace elt set)) to assure set is always properly modified.
+(defun set:replace-key-value (key value set)
+  "Replace or add element whose car matches KEY with a cdr of VALUE in SET.
+Return the set.  Use (setq set (set:replace elt set)) to assure set is
+always properly modified. 
 
 Use `set:equal-op' to match against KEY.  Assume each element in the set has a
 car and a cdr."
diff --git a/test/MANIFEST b/test/MANIFEST
index 2700c67646..818c61174c 100644
--- a/test/MANIFEST
+++ b/test/MANIFEST
@@ -19,4 +19,5 @@ hy-test-helpers.el      - unit test helpers
 kexport-tests.el        - kexport tests
 kotl-mode-tests.el      - kotl-mode-el tests
 kotl-orgtbl-tests.el    - kotl orgtbl tests
+set-tests.el            - mathematical set library tests
 smart-org-tests.el      - smart-org-el tests
diff --git a/test/set-tests.el b/test/set-tests.el
new file mode 100644
index 0000000000..7cb920772a
--- /dev/null
+++ b/test/set-tests.el
@@ -0,0 +1,143 @@
+;;; set-tests.el --- Hyperbole mathematical set library tests          -*- 
lexical-binding: t; -*-
+;;
+;; Author:       Mats Lidell
+;;
+;; Orig-Date:     5-Feb-23 at 09:12:52
+;; Last-Mod:      6-Feb-23 at 02:06:27 by Bob Weiner
+;;
+;; SPDX-License-Identifier: GPL-3.0-or-later
+;;
+;; Copyright (C) 2021-2022  Free Software Foundation, Inc.
+;; See the "HY-COPY" file for license information.
+;;
+;; This file is part of GNU Hyperbole.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'set)
+
+(ert-deftest set-tests--function-tests ()
+  "Test Hyperbole set library functions."
+  (should (set:equal (set:create) nil))
+  (should (set:empty (set:create)))
+  (should-not (set:empty (set:create 'a)))
+
+  (should (set:is (set:create)))
+  (should (set:is (set:create 'a)))
+  (should-not (set:is "string is not a set"))
+  (should-not (set:is 'symbol-is-not-a-set))
+  (should-not (set:is 1))
+  (should-not (set:is '(a a)))
+
+  (should (= (set:size (set:create)) 0))
+  (should (= (set:size (set:create 'a)) 1))
+  (should (= (set:size (set:create 'a 'b)) 2))
+  (should (= (set:size (set:create 'a 'a)) 1))
+
+  (should (set:equal (set:create) (set:create)))
+  (should (set:equal (set:create 'a) (set:create 'a)))
+  (should (set:equal (set:create 'b 'a) (set:create 'a 'b)))
+
+  (should (set:equal (set:member 'a (set:create 'a)) (set:create 'a)))
+  (should (set:equal (set:member 'a (set:create 'a 'b)) (set:create 'a 'b)))
+  (should (set:equal (set:member 'b (set:create 'a 'b)) (set:create 'b)))
+  (should (set:empty (set:member 'c (set:create 'a))))
+
+  (should (set:is (set:combinations (set:create 'a 'b 'c))))
+  (should (set:equal (set:combinations (set:create 'a)) '(nil a)))
+  (should (set:equal (set:combinations (set:create 'a 'b)) '(nil a b (a b))))
+  (should (set:equal (set:combinations (set:create 'a 'b 'c))
+                     '(nil a b c (a b) (a c) (b c) (a b c))))
+
+  (should (set:empty (set:intersection (set:create) (set:create))))
+  (should (set:empty (set:intersection (set:create) (set:create 'a))))
+  (should (set:empty (set:intersection (set:create 'a) (set:create 'c))))
+  (should (set:equal (set:intersection (set:create 'a) (set:create 'a))
+                     (set:create 'a)))
+  (should (set:equal (set:intersection (set:create 'a 'b) (set:create 'a))
+                     (set:create 'a)))
+  (should (set:equal (set:intersection (set:create 'a 'b) (set:create 'a 'c) 
(set:create 'a 'd))
+                     (set:create 'a)))
+
+  (should (set:empty (set:difference (set:create) (set:create))))
+  (should (set:empty (set:difference (set:create 'a) (set:create 'a))))
+  (should (set:empty (set:difference (set:create) (set:create 'a))))
+  (should (set:equal (set:difference (set:create 'a) (set:create))
+                     (set:create 'a)))
+  (should (set:equal (set:difference (set:create 'a) (set:create 'c))
+                     (set:create 'a)))
+  (should (set:equal (set:difference (set:create 'a 'b) (set:create 'a))
+                     (set:create 'b)))
+  (should (set:empty (set:difference (set:create 'a 'b) (set:create 'a) 
(set:create 'b))))
+
+  (should (set:empty (set:union (set:create) (set:create))))
+  (should (set:equal (set:union (set:create 'a) (set:create))
+                     (set:create 'a)))
+  (should (set:equal (set:union (set:create 'a) (set:create 'a))
+                     (set:create 'a)))
+  (should (set:equal (set:union (set:create 'a) (set:create 'b))
+                     (set:create 'a 'b)))
+  (should (set:equal (set:union (set:create 'a) (set:create 'b) (set:create 
'c))
+                     (set:create 'a 'b 'c)))
+
+  (should (set:empty (set:difference (set:create) (set:create 'a))))
+  (should (set:empty (set:difference (set:create 'a) (set:create 'a))))
+  (should (set:equal (set:difference (set:create 'a) (set:create))
+                     (set:create 'a)))
+  (should (set:equal (set:difference (set:create 'a) (set:create 'c))
+                     (set:create 'a)))
+  (should (set:equal (set:difference (set:create 'a 'b) (set:create 'a))
+                     (set:create 'b)))
+  (should (set:empty (set:difference (set:create 'a 'b) (set:create 'a) 
(set:create 'b))))
+
+  (should (set:subset (set:create) (set:create)))
+  (should (set:subset (set:create) (set:create 'a)))
+  (should (set:subset (set:create 'a) (set:create 'a)))
+  (should-not (set:subset (set:create 'a) (set:create 'b)))
+
+  (should (set:equal (set:add 'a (set:create)) (set:create 'a)))
+  (should (set:equal (set:add 'a (set:create 'a)) (set:create 'a)))
+  ;; Adding a list as an element in a set
+  (should (set:equal (set:add '(b c) (set:create 'a)) (set:create 'a '(b c))))
+
+  (should (set:empty (set:remove 'a (set:create 'a))))
+  (should (set:equal (set:remove 'a (set:create 'b)) (set:create 'b)))
+  (should (set:equal (set:remove 'a (set:create 'a 'b)) (set:create 'b)))
+  (should-not (set:equal (set:remove 'a (set:create 'a 'b)) (set:create 'a 
'b)))
+
+  ;; FIXME: Need to add tests for (set:remove-key-value key set)
+
+  ;; set:get - requires elements to be of type (key . value)
+  (should (equal (set:get 'a (set:create (cons 'a 'value-a))) 'value-a))
+  (should (equal (set:get 'b (set:create (cons 'a 'value-a))) nil))
+
+  ;; FIXME: Need to add tests for (set:replace old-val new-val set)
+
+  ;; set:replace-key-value - requires elements to be of type (key . value)
+  (should (set:equal (set:replace-key-value 'a 'new-value-a (set:create (cons 
'a 'value-a)))
+                     (set:create '(a . new-value-a) '(a . value-a))))
+  (should (set:equal (set:replace-key-value 'b 'new-value-b (set:create (cons 
'a 'value-a)))
+                     (set:create '(b . new-value-b) '(a . value-a))))
+  (let ((set (set:create (cons 'a 'value-a))))
+    (setq set (set:replace-key-value 'a 'new-value-a set))
+    (should (set:equal set (set:create '(a . new-value-a) '(a . value-a)))))
+  
+  ;; set:members works on lists!?
+  ;; FIXME: Use verification that list contains elements rather than checking
+  ;; a specific order
+  (should (equal (set:members '(1)) '(1)))
+  (should (equal (set:members '(1 2)) '(1 2)))
+  (should (equal (set:members '(1 1)) '(1)))
+  (should (equal (set:members '(1 1 2 2)) '(1 2))))
+
+(ert-deftest set-tests--equal-op-tests ()
+  "Test Hyperbole set library functions with equal op always true."
+  (let ((set:equal-op (lambda (_x _y) t)))
+    (should (= (set:size (set:create 'a 'b)) 1))
+    (should (= (set:size (set:union (set:create 'a) (set:create 'b))) 1))
+    (should (set:equal (set:union (set:create 'a) (set:create 'b)) (set:create 
'a)))))
+
+(provide 'set-tests)
+;;; set-tests.el ends here

Reply via email to