branch: externals/compat
commit 31899a2a4d04335512a52f120087f699c00da693
Author: Daniel Mendler <m...@daniel-mendler.de>
Commit: Daniel Mendler <m...@daniel-mendler.de>

    compat-30: Add sort-on
---
 NEWS.org        |  1 +
 compat-30.el    | 21 +++++++++++++++++++++
 compat-tests.el |  9 +++++++++
 compat.texi     | 20 ++++++++++++++++++++
 4 files changed, 51 insertions(+)

diff --git a/NEWS.org b/NEWS.org
index 4fbb0bcaa2..f1c380f9b3 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -4,6 +4,7 @@
 
 * Development
 
+- compat-30: Add =sort-on=.
 - compat-30: Add extended =copy-tree= with support for copying records with
   non-nil optional second argument.
 - compat-30: New macro =static-if=.
diff --git a/compat-30.el b/compat-30.el
index cd9ad76ea1..a831c4e2af 100644
--- a/compat-30.el
+++ b/compat-30.el
@@ -27,6 +27,27 @@
 ;; TODO Update to 30.1 as soon as the Emacs emacs-30 branch version bumped
 (compat-version "30.0.50")
 
+;;;; Defined in sort.el
+
+(compat-defun sort-on (sequence predicate accessor) ;; <compat-tests:sort-on>
+  "Sort SEQUENCE by calling PREDICATE on sort keys produced by ACCESSOR.
+SEQUENCE should be the input list to sort.
+Elements of SEQUENCE are sorted by keys which are obtained by
+calling ACCESSOR on each element.  ACCESSOR should be a function of
+one argument, an element of SEQUENCE, and should return the key
+value to be compared by PREDICATE for sorting the element.
+PREDICATE is the function for comparing keys; it is called with two
+arguments, the keys to compare, and should return non-nil if the
+first key should sort before the second key.
+This function has the performance advantage of evaluating
+ACCESSOR only once for each element in the input SEQUENCE, and is
+therefore appropriate when computing the key by ACCESSOR is an
+expensive operation.  This is known as the \"decorate-sort-undecorate\"
+paradigm, or the Schwartzian transform."
+  (mapcar #'car
+          (sort (mapcar #'(lambda (x) (cons x (funcall accessor x))) sequence)
+                #'(lambda (x y) (funcall predicate (cdr x) (cdr y))))))
+
 ;;;; Defined in buffer.c
 
 (compat-defun find-buffer (variable value) ;; <compat-tests:find-buffer>
diff --git a/compat-tests.el b/compat-tests.el
index 8340d5fc0b..71bcc08d79 100644
--- a/compat-tests.el
+++ b/compat-tests.el
@@ -1753,6 +1753,15 @@
   (should-equal '(1 2 3 4) (flatten-tree '((1) nil 2 ((3 4)))))
   (should-equal '(1 2 3 4) (flatten-tree '(((1 nil)) 2 (((3 nil nil) 4))))))
 
+(ert-deftest compat-sort-on ()
+  ;; TODO enable if CI emacs 30 supports sort-on
+  (static-if (< emacs-major-version 30)
+    (progn
+      (should-equal '(3 2 1) (sort-on '(2 1 3) #'> #'identity))
+      (should-equal '(1 2 3) (sort-on [2 1 3] #'< #'identity))
+      (should-equal '((1 z) (2 y) (3 x)) (sort-on (list '(2 y) '(1 z) '(3 x)) 
#'< #'car))
+      (should-equal '((x 3) (y 2) (z 1)) (sort-on (list '(y 2) '(z 1) '(x 3)) 
#'> #'cadr)))))
+
 (ert-deftest compat-sort ()
   (should-equal (list 1 2 3) (sort (list 1 2 3) #'<))
   (should-equal (list 1 2 3) (sort (list 1 3 2) #'<))
diff --git a/compat.texi b/compat.texi
index f80a622d51..066e0b187e 100644
--- a/compat.texi
+++ b/compat.texi
@@ -3348,6 +3348,26 @@ older than 30.1.  Note that due to upstream changes, it 
might happen
 that there will be the need for changes, so use these functions with
 care.
 
+@c copied from lispref/sequences.texi
+@defun sort-on sequence predicate accessor
+This function stably sorts the list @var{sequence}, comparing the sort
+keys of the elements using @var{predicate}.  The comparison function
+@var{predicate} accepts two arguments, the sort keys to compare, and
+should return non-@code{nil} if the element corresponding to the first
+key should sort before the element corresponding to the second key.
+The function computes a sort key of each element by calling the
+@var{accessor} function on that element; it does so exactly once for
+each element of @var{sequence}.  The @var{accessor} function is called
+with a single argument, an element of @var{sequence}.
+
+This function implements what is known as
+@dfn{decorate-sort-undecorate} paradigm, of the Schwartzian transform.
+It basically trades CPU for memory, creating a temporary list with the
+computed sport keys, then mapping @code{car} over the result of
+sorting that temporary list.  Unlike with @code{sort}, the return list
+is a copy; the original list is left intact.
+@end defun
+
 @defun get-truename-buffer filename
 Return the buffer with @code{file-truename} equal to @var{filename} (a string).
 If there is no such live buffer, return nil.

Reply via email to