unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#56199: hash table equality predicate [PATCH]
@ 2022-06-24 17:19 Mattias Engdegård
  2022-06-24 18:21 ` Lars Ingebrigtsen
  0 siblings, 1 reply; 4+ messages in thread
From: Mattias Engdegård @ 2022-06-24 17:19 UTC (permalink / raw)
  To: 56199

[-- Attachment #1: Type: text/plain, Size: 600 bytes --]

Recently[1] a predicate for structural equality was requested that also recurses through hash tables.
It showed that Emacs doesn't even come with a way of comparing hash tables. Third-party implementations exist but if the code quoted in [2] is representative, perhaps it would make sense to add a `hash-table-equal-p` predicate?
Even implemented entirely in Lisp it would be an order of magnitude faster (and actually correct).

The attached code is not without flaws but provides a rough starting point.
(This is not meant as a strong argument for or against adding it in the first place.)


[-- Attachment #2: hash-table-equal-p.diff --]
[-- Type: application/octet-stream, Size: 3636 bytes --]

diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index 9cd793d05c..17ca80a297 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -93,6 +93,28 @@ hash-table-values
   "Return a list of values in HASH-TABLE."
   (cl-loop for v being the hash-values of hash-table collect v))
 
+(defun hash-table-equal-p (h1 h2 &optional value-eq)
+  "Whether the hash tables H1 and H2 are equal with respect to VALUE-EQ.
+Equality means that the tables have the same equality predicate
+and the same set of key-value pairs where keys are compared by
+that predicate and values by VALUE-EQ, which defaults to `eq'."
+  (or (eq h1 h2)
+      (and (= (hash-table-count h1) (hash-table-count h2))
+           (eq (hash-table-test h1) (hash-table-test h2))
+           (progn
+             (unless value-eq
+               (setq value-eq #'eq))
+             ;; Loop over the physically smaller table.
+             (when (> (hash-table-size h1) (hash-table-size h2))
+               (cl-rotatef h1 h2))
+             (catch 'done
+               (maphash
+                (lambda (k v)
+                  (unless (funcall value-eq v (gethash k h2 (not v)))
+                    (throw 'done nil)))
+                h1)
+               t)))))
+
 (defsubst string-empty-p (string)
   "Check whether STRING is empty."
   (string= string ""))
diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el
index 7f3916c2c0..923155eedb 100644
--- a/test/lisp/emacs-lisp/subr-x-tests.el
+++ b/test/lisp/emacs-lisp/subr-x-tests.el
@@ -743,6 +743,55 @@ test-with-buffer-unmodified-if-unchanged
           (with-current-buffer inner
             (should-not (buffer-modified-p))))))))
 
+(ert-deftest subr-x--hash-table-equal-p ()
+  (cl-flet ((hashtab (test &rest elts)
+              (let ((h (make-hash-table :test test)))
+                (while elts
+                  (let* ((key (pop elts))
+                         (val (pop elts)))
+                    (puthash key val h)))
+                h)))
+
+    (let ((h1 (hashtab #'eq 'a (list 1) 'b (list 2))
+          (h2 (hashtab #'eq 'a (list 1) 'b (list 2)))))
+      (should (hash-table-equal-p h1 h2 #'equal))
+      (should (hash-table-equal-p h2 h1 #'equal))
+      (should (not (hash-table-equal-p h1 h2 #'eq)))
+      (should (not (hash-table-equal-p h2 h1 #'eq)))
+      (should (hash-table-equal-p h1 h1 #'eq)))
+
+    (let ((h1 (hashtab #'eql 1 'a 2 'b)
+          (h2 (hashtab #'equal 1 'a 2 'b))))
+      (should (not (hash-table-equal-p h1 h2)))
+      (should (not (hash-table-equal-p h2 h1))))
+
+    (let ((h1 (hashtab #'eql 1 'a 2 'a)
+          (h2 (hashtab #'eql 1 'a))))
+      (should (not (hash-table-equal-p h1 h2)))
+      (should (not (hash-table-equal-p h2 h1))))
+
+    (let ((h1 (hashtab #'eql 1 'a 2 'a)
+          (h2 (hashtab #'eql 1 'a 2 'b))))
+      (should (not (hash-table-equal-p h1 h2)))
+      (should (not (hash-table-equal-p h2 h1))))
+
+    (let ((h1 (hashtab #'eql 1 'a 2 'a)
+          (h2 (hashtab #'eql 1 'a 3 'a))))
+      (should (not (hash-table-equal-p h1 h2)))
+      (should (not (hash-table-equal-p h2 h1))))
+
+    (let ((h1 (hashtab #'eql)
+          (h2 (hashtab #'eql))))
+      (should (hash-table-equal-p h1 h2))
+      (should (hash-table-equal-p h2 h1)))
+
+    (let ((h1 (make-hash-table :test #'eql :size 1000 :rehash-size 3.5))
+          (h2 (hashtab #'eql 10 'a 20 'b)))
+      (puthash 10 'a h1)
+      (puthash 20 'b h1)
+      (should (hash-table-equal-p h1 h2))
+      (should (hash-table-equal-p h2 h1)))
+    ))
 
 (provide 'subr-x-tests)
 ;;; subr-x-tests.el ends here

[-- Attachment #3: Type: text/plain, Size: 153 bytes --]



--

[1] https://lists.gnu.org/archive/html/emacs-devel/2022-06/msg00444.html
[2] https://lists.gnu.org/archive/html/emacs-devel/2022-06/msg00553.html


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* bug#56199: hash table equality predicate [PATCH]
  2022-06-24 17:19 bug#56199: hash table equality predicate [PATCH] Mattias Engdegård
@ 2022-06-24 18:21 ` Lars Ingebrigtsen
  2022-06-25 16:56   ` Mattias Engdegård
  0 siblings, 1 reply; 4+ messages in thread
From: Lars Ingebrigtsen @ 2022-06-24 18:21 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: 56199

Mattias Engdegård <mattiase@acm.org> writes:

> Even implemented entirely in Lisp it would be an order of magnitude
> faster (and actually correct).
>
> The attached code is not without flaws but provides a rough starting point.
> (This is not meant as a strong argument for or against adding it in
> the first place.)

I can't ever recall wanting to compare two hash tables for equality
(like, that's not what you use a hash table for), but since people have
apparently been reimplementing this a lot, then I'm for including it.

And the implementation looks nice.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#56199: hash table equality predicate [PATCH]
  2022-06-24 18:21 ` Lars Ingebrigtsen
@ 2022-06-25 16:56   ` Mattias Engdegård
  2022-06-26 14:30     ` Jean Louis
  0 siblings, 1 reply; 4+ messages in thread
From: Mattias Engdegård @ 2022-06-25 16:56 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 56199-done

24 juni 2022 kl. 20.21 skrev Lars Ingebrigtsen <larsi@gnus.org>:

> I can't ever recall wanting to compare two hash tables for equality
> (like, that's not what you use a hash table for)

No, you're right -- it's better to wait until there is a concrete need for it.






^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#56199: hash table equality predicate [PATCH]
  2022-06-25 16:56   ` Mattias Engdegård
@ 2022-06-26 14:30     ` Jean Louis
  0 siblings, 0 replies; 4+ messages in thread
From: Jean Louis @ 2022-06-26 14:30 UTC (permalink / raw)
  To: Mattias Engdegård, Lars Ingebrigtsen; +Cc: 56199-done

I use database stored hashes where new edited and updated one shall be compared to the stored one.

This will become useful.


Jean





^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2022-06-26 14:30 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-06-24 17:19 bug#56199: hash table equality predicate [PATCH] Mattias Engdegård
2022-06-24 18:21 ` Lars Ingebrigtsen
2022-06-25 16:56   ` Mattias Engdegård
2022-06-26 14:30     ` Jean Louis

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).