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.