From ae0ce7959e647c2a5a9553fd4b8dee844e2fa357 Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Sun, 5 Jun 2022 19:41:22 +0300 Subject: [PATCH] Improve element finding * dash.el (-first): Contrast -first with -first-item, not car, in docstring. (-elem-index, --find-index, --find-last-index): Avoid constructing a list of results just to throw most of them away. Suggested by Philippe Vaucher (#394). (--find-indices): Use --keep for speed. (-elem-indices, -find-indices): Use --find-indices for speed. (-find-index, -find-last-index): Use anaphoric counterpart for speed. * dev/examples.el (-elem-index, -elem-indices, -find-index) (-find-last-index, -find-indices): Extend tests. * README.md: * dash.texi: Regenerate docs. Fixes #394. --- README.md | 75 ++++++++++++++++++++------------ dash.el | 110 +++++++++++++++++++++++++++++++---------------- dash.texi | 93 ++++++++++++++++++++++++---------------- dev/examples.el | 112 ++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 277 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 885f5f0..90607f6 100644 --- a/README.md +++ b/README.md @@ -1669,64 +1669,83 @@ related predicates. #### -elem-index `(elem list)` -Return the index of the first element in the given `list` which -is equal to the query element `elem`, or `nil` if there is no -such element. +Return the first index of `elem` in `list`. +That is, the index within `list` of the first element that is +`equal` to `elem`. Return `nil` if there is no such element. + +See also: [`-find-index`](#-find-index-pred-list). ```el -(-elem-index 2 '(6 7 8 2 3 4)) ;; => 3 +(-elem-index 2 '(6 7 8 3 4)) ;; => nil (-elem-index "bar" '("foo" "bar" "baz")) ;; => 1 (-elem-index '(1 2) '((3) (5 6) (1 2) nil)) ;; => 2 ``` #### -elem-indices `(elem list)` -Return the indices of all elements in `list` equal to the query -element `elem`, in ascending order. +Return the list of indices at which `elem` appears in `list`. +That is, the indices of all elements of `list` `equal` to `elem`, in +the same ascending order as they appear in `list`. ```el -(-elem-indices 2 '(6 7 8 2 3 4 2 1)) ;; => (3 6) +(-elem-indices 2 '(6 7 8 3 4 1)) ;; => () (-elem-indices "bar" '("foo" "bar" "baz")) ;; => (1) (-elem-indices '(1 2) '((3) (1 2) (5 6) (1 2) nil)) ;; => (1 3) ``` #### -find-index `(pred list)` -Take a predicate `pred` and a `list` and return the index of the -first element in the list satisfying the predicate, or `nil` if -there is no such element. +Return the index of the first item satisfying `pred` in `list`. +Return `nil` if no such item is found. + +`pred` is called with one argument, the current list element, until +it returns non-`nil`, at which point the search terminates. + +This function's anaphoric counterpart is `--find-index`. -See also [`-first`](#-first-pred-list). +See also: [`-first`](#-first-pred-list), [`-find-last-index`](#-find-last-index-pred-list). ```el -(-find-index 'even? '(2 4 1 6 3 3 5 8)) ;; => 0 -(--find-index (< 5 it) '(2 4 1 6 3 3 5 8)) ;; => 3 -(-find-index (-partial 'string-lessp "baz") '("bar" "foo" "baz")) ;; => 1 +(-find-index #'numberp '(a b c)) ;; => nil +(-find-index #'natnump '(1 0 -1)) ;; => 0 +(--find-index (> it 5) '(2 4 1 6 3 3 5 8)) ;; => 3 ``` #### -find-last-index `(pred list)` -Take a predicate `pred` and a `list` and return the index of the -last element in the list satisfying the predicate, or `nil` if -there is no such element. +Return the index of the last item satisfying `pred` in `list`. +Return `nil` if no such item is found. + +Predicate `pred` is called with one argument each time, namely the +current list element. + +This function's anaphoric counterpart is `--find-last-index`. -See also [`-last`](#-last-pred-list). +See also: [`-last`](#-last-pred-list), [`-find-index`](#-find-index-pred-list). ```el -(-find-last-index 'even? '(2 4 1 6 3 3 5 8)) ;; => 7 -(--find-last-index (< 5 it) '(2 7 1 6 3 8 5 2)) ;; => 5 -(-find-last-index (-partial 'string-lessp "baz") '("q" "foo" "baz")) ;; => 1 +(-find-last-index #'numberp '(a b c)) ;; => nil +(--find-last-index (> it 5) '(2 7 1 6 3 8 5 2)) ;; => 5 +(-find-last-index (-partial #'string< 'a) '(c b a)) ;; => 1 ``` #### -find-indices `(pred list)` -Return the indices of all elements in `list` satisfying the -predicate `pred`, in ascending order. +Return the list of indices in `list` satisfying `pred`. + +Each element of `list` in turn is passed to `pred`. If the result is +non-`nil`, the index of that element in `list` is included in the +result. The returned indices are in ascending order, i.e., in +the same order as they appear in `list`. + +This function's anaphoric counterpart is `--find-indices`. + +See also: [`-find-index`](#-find-index-pred-list), [`-elem-indices`](#-elem-indices-elem-list). ```el -(-find-indices 'even? '(2 4 1 6 3 3 5 8)) ;; => (0 1 3 7) -(--find-indices (< 5 it) '(2 4 1 6 3 3 5 8)) ;; => (3 7) -(-find-indices (-partial 'string-lessp "baz") '("bar" "foo" "baz")) ;; => (1) +(-find-indices #'numberp '(a b c)) ;; => () +(-find-indices #'numberp '(8 1 d 2 b c a 3)) ;; => (0 1 3 7) +(--find-indices (> it 5) '(2 4 1 6 3 3 5 8)) ;; => (3 7) ``` #### -grade-up `(comparator list)` @@ -2069,7 +2088,9 @@ See also: [`-flatten-n`](#-flatten-n-num-list), [`-table`](#-table-fn-rest-lists Return the first item in `list` for which `pred` returns non-`nil`. Return `nil` if no such element is found. -To get the first item in the list no questions asked, use `car`. + +To get the first item in the list no questions asked, +use [`-first-item`](#-first-item-list). Alias: `-find`. diff --git a/dash.el b/dash.el index 63de240..7a90e7b 100644 --- a/dash.el +++ b/dash.el @@ -858,14 +858,16 @@ This is the anaphoric counterpart to `-first'." (defun -first (pred list) "Return the first item in LIST for which PRED returns non-nil. Return nil if no such element is found. -To get the first item in the list no questions asked, use `car'. + +To get the first item in the list no questions asked, +use `-first-item'. Alias: `-find'. This function's anaphoric counterpart is `--first'." (--first (funcall pred it) list)) -(defalias '-find '-first) +(defalias '-find #'-first) (defalias '--find '--first) (defmacro --some (form list) @@ -1780,54 +1782,88 @@ See also: `-flatten-n', `-table'" (dash--table-carry lists restore-lists))) (nreverse re))) +(defmacro --find-index (form list) + "Return the first index in LIST for which FORM evals to non-nil. +Return nil if no such index is found. +Each element of LIST in turn is bound to `it' and its index +within LIST to `it-index' before evaluating FORM. +This is the anaphoric counterpart to `-find-index'." + (declare (debug (form form))) + `(--some (and ,form it-index) ,list)) + +(defun -find-index (pred list) + "Return the index of the first item satisfying PRED in LIST. +Return nil if no such item is found. + +PRED is called with one argument, the current list element, until +it returns non-nil, at which point the search terminates. + +This function's anaphoric counterpart is `--find-index'. + +See also: `-first', `-find-last-index'." + (--find-index (funcall pred it) list)) + (defun -elem-index (elem list) - "Return the index of the first element in the given LIST which -is equal to the query element ELEM, or nil if there is no -such element." - (declare (pure t) (side-effect-free t)) - (car (-elem-indices elem list))) + "Return the first index of ELEM in LIST. +That is, the index within LIST of the first element that is +`equal' to ELEM. Return nil if there is no such element. -(defun -elem-indices (elem list) - "Return the indices of all elements in LIST equal to the query -element ELEM, in ascending order." +See also: `-find-index'." (declare (pure t) (side-effect-free t)) - (-find-indices (-partial 'equal elem) list)) + (--find-index (equal elem it) list)) + +(defmacro --find-indices (form list) + "Return the list of indices in LIST for which FORM evals to non-nil. +Each element of LIST in turn is bound to `it' and its index +within LIST to `it-index' before evaluating FORM. +This is the anaphoric counterpart to `-find-indices'." + (declare (debug (form form))) + `(--keep (and ,form it-index) ,list)) (defun -find-indices (pred list) - "Return the indices of all elements in LIST satisfying the -predicate PRED, in ascending order." - (apply 'append (--map-indexed (when (funcall pred it) (list it-index)) list))) + "Return the list of indices in LIST satisfying PRED. -(defmacro --find-indices (form list) - "Anaphoric version of `-find-indices'." - (declare (debug (def-form form))) - `(-find-indices (lambda (it) (ignore it) ,form) ,list)) +Each element of LIST in turn is passed to PRED. If the result is +non-nil, the index of that element in LIST is included in the +result. The returned indices are in ascending order, i.e., in +the same order as they appear in LIST. -(defun -find-index (pred list) - "Take a predicate PRED and a LIST and return the index of the -first element in the list satisfying the predicate, or nil if -there is no such element. +This function's anaphoric counterpart is `--find-indices'. -See also `-first'." - (car (-find-indices pred list))) +See also: `-find-index', `-elem-indices'." + (--find-indices (funcall pred it) list)) -(defmacro --find-index (form list) - "Anaphoric version of `-find-index'." - (declare (debug (def-form form))) - `(-find-index (lambda (it) (ignore it) ,form) ,list)) +(defun -elem-indices (elem list) + "Return the list of indices at which ELEM appears in LIST. +That is, the indices of all elements of LIST `equal' to ELEM, in +the same ascending order as they appear in LIST." + (declare (pure t) (side-effect-free t)) + (--find-indices (equal elem it) list)) + +(defmacro --find-last-index (form list) + "Return the last index in LIST for which FORM evals to non-nil. +Return nil if no such index is found. +Each element of LIST in turn is bound to `it' and its index +within LIST to `it-index' before evaluating FORM. +This is the anaphoric counterpart to `-find-last-index'." + (declare (debug (form form))) + (let ((i (make-symbol "index"))) + `(let (,i) + (--each ,list + (when ,form (setq ,i it-index))) + ,i))) (defun -find-last-index (pred list) - "Take a predicate PRED and a LIST and return the index of the -last element in the list satisfying the predicate, or nil if -there is no such element. + "Return the index of the last item satisfying PRED in LIST. +Return nil if no such item is found. -See also `-last'." - (-last-item (-find-indices pred list))) +Predicate PRED is called with one argument each time, namely the +current list element. -(defmacro --find-last-index (form list) - "Anaphoric version of `-find-last-index'." - (declare (debug (def-form form))) - `(-find-last-index (lambda (it) (ignore it) ,form) ,list)) +This function's anaphoric counterpart is `--find-last-index'. + +See also: `-last', `-find-index'." + (--find-last-index (funcall pred it) list)) (defun -select-by-indices (indices list) "Return a list whose elements are elements from LIST selected diff --git a/dash.texi b/dash.texi index a3c22bd..919487b 100644 --- a/dash.texi +++ b/dash.texi @@ -2395,14 +2395,16 @@ related predicates. @anchor{-elem-index} @defun -elem-index (elem list) -Return the index of the first element in the given @var{list} which -is equal to the query element @var{elem}, or @code{nil} if there is no -such element. +Return the first index of @var{elem} in @var{list}. +That is, the index within @var{list} of the first element that is +@code{equal} to @var{elem}. Return @code{nil} if there is no such element. + +See also: @code{-find-index} (@pxref{-find-index}). @example @group -(-elem-index 2 '(6 7 8 2 3 4)) - @result{} 3 +(-elem-index 2 '(6 7 8 3 4)) + @result{} nil @end group @group (-elem-index "bar" '("foo" "bar" "baz")) @@ -2417,13 +2419,14 @@ such element. @anchor{-elem-indices} @defun -elem-indices (elem list) -Return the indices of all elements in @var{list} equal to the query -element @var{elem}, in ascending order. +Return the list of indices at which @var{elem} appears in @var{list}. +That is, the indices of all elements of @var{list} @code{equal} to @var{elem}, in +the same ascending order as they appear in @var{list}. @example @group -(-elem-indices 2 '(6 7 8 2 3 4 2 1)) - @result{} (3 6) +(-elem-indices 2 '(6 7 8 3 4 1)) + @result{} () @end group @group (-elem-indices "bar" '("foo" "bar" "baz")) @@ -2438,47 +2441,55 @@ element @var{elem}, in ascending order. @anchor{-find-index} @defun -find-index (pred list) -Take a predicate @var{pred} and a @var{list} and return the index of the -first element in the list satisfying the predicate, or @code{nil} if -there is no such element. +Return the index of the first item satisfying @var{pred} in @var{list}. +Return @code{nil} if no such item is found. -See also @code{-first} (@pxref{-first}). +@var{pred} is called with one argument, the current list element, until +it returns non-@code{nil}, at which point the search terminates. + +This function's anaphoric counterpart is @code{--find-index}. + +See also: @code{-first} (@pxref{-first}), @code{-find-last-index} (@pxref{-find-last-index}). @example @group -(-find-index 'even? '(2 4 1 6 3 3 5 8)) - @result{} 0 +(-find-index #'numberp '(a b c)) + @result{} nil @end group @group -(--find-index (< 5 it) '(2 4 1 6 3 3 5 8)) - @result{} 3 +(-find-index #'natnump '(1 0 -1)) + @result{} 0 @end group @group -(-find-index (-partial 'string-lessp "baz") '("bar" "foo" "baz")) - @result{} 1 +(--find-index (> it 5) '(2 4 1 6 3 3 5 8)) + @result{} 3 @end group @end example @end defun @anchor{-find-last-index} @defun -find-last-index (pred list) -Take a predicate @var{pred} and a @var{list} and return the index of the -last element in the list satisfying the predicate, or @code{nil} if -there is no such element. +Return the index of the last item satisfying @var{pred} in @var{list}. +Return @code{nil} if no such item is found. + +Predicate @var{pred} is called with one argument each time, namely the +current list element. -See also @code{-last} (@pxref{-last}). +This function's anaphoric counterpart is @code{--find-last-index}. + +See also: @code{-last} (@pxref{-last}), @code{-find-index} (@pxref{-find-index}). @example @group -(-find-last-index 'even? '(2 4 1 6 3 3 5 8)) - @result{} 7 +(-find-last-index #'numberp '(a b c)) + @result{} nil @end group @group -(--find-last-index (< 5 it) '(2 7 1 6 3 8 5 2)) +(--find-last-index (> it 5) '(2 7 1 6 3 8 5 2)) @result{} 5 @end group @group -(-find-last-index (-partial 'string-lessp "baz") '("q" "foo" "baz")) +(-find-last-index (-partial #'string< 'a) '(c b a)) @result{} 1 @end group @end example @@ -2486,21 +2497,29 @@ See also @code{-last} (@pxref{-last}). @anchor{-find-indices} @defun -find-indices (pred list) -Return the indices of all elements in @var{list} satisfying the -predicate @var{pred}, in ascending order. +Return the list of indices in @var{list} satisfying @var{pred}. + +Each element of @var{list} in turn is passed to @var{pred}. If the result is +non-@code{nil}, the index of that element in @var{list} is included in the +result. The returned indices are in ascending order, i.e., in +the same order as they appear in @var{list}. + +This function's anaphoric counterpart is @code{--find-indices}. + +See also: @code{-find-index} (@pxref{-find-index}), @code{-elem-indices} (@pxref{-elem-indices}). @example @group -(-find-indices 'even? '(2 4 1 6 3 3 5 8)) - @result{} (0 1 3 7) +(-find-indices #'numberp '(a b c)) + @result{} () @end group @group -(--find-indices (< 5 it) '(2 4 1 6 3 3 5 8)) - @result{} (3 7) +(-find-indices #'numberp '(8 1 d 2 b c a 3)) + @result{} (0 1 3 7) @end group @group -(-find-indices (-partial 'string-lessp "baz") '("bar" "foo" "baz")) - @result{} (1) +(--find-indices (> it 5) '(2 4 1 6 3 3 5 8)) + @result{} (3 7) @end group @end example @end defun @@ -3072,7 +3091,9 @@ See also: @code{-flatten-n} (@pxref{-flatten-n}), @code{-table} (@pxref{-table}) @defun -first (pred list) Return the first item in @var{list} for which @var{pred} returns non-@code{nil}. Return @code{nil} if no such element is found. -To get the first item in the list no questions asked, use @code{car}. + +To get the first item in the list no questions asked, +use @code{-first-item} (@pxref{-first-item}). Alias: @code{-find}. diff --git a/dev/examples.el b/dev/examples.el index 501e205..9231c73 100644 --- a/dev/examples.el +++ b/dev/examples.el @@ -989,29 +989,115 @@ value rather than consuming a list to produce a single value." related predicates." (defexamples -elem-index - (-elem-index 2 '(6 7 8 2 3 4)) => 3 + (-elem-index 2 '(6 7 8 3 4)) => nil (-elem-index "bar" '("foo" "bar" "baz")) => 1 - (-elem-index '(1 2) '((3) (5 6) (1 2) nil)) => 2) + (-elem-index '(1 2) '((3) (5 6) (1 2) nil)) => 2 + (-elem-index nil ()) => nil + (-elem-index nil '(t)) => nil + (-elem-index nil '(nil)) => 0 + (-elem-index nil '(nil t)) => 0 + (-elem-index nil '(t nil)) => 1 + (-elem-index t ()) => nil + (-elem-index t '(nil)) => nil + (-elem-index t '(t)) => 0 + (-elem-index t '(t nil)) => 0 + (-elem-index t '(nil t)) => 1) (defexamples -elem-indices - (-elem-indices 2 '(6 7 8 2 3 4 2 1)) => '(3 6) + (-elem-indices 2 '(6 7 8 3 4 1)) => '() (-elem-indices "bar" '("foo" "bar" "baz")) => '(1) - (-elem-indices '(1 2) '((3) (1 2) (5 6) (1 2) nil)) => '(1 3)) + (-elem-indices '(1 2) '((3) (1 2) (5 6) (1 2) nil)) => '(1 3) + (-elem-indices nil ()) => () + (-elem-indices nil '(t)) => () + (-elem-indices nil '(nil)) => '(0) + (-elem-indices nil '(nil t)) => '(0) + (-elem-indices nil '(t nil)) => '(1) + (-elem-indices nil '(t t)) => () + (-elem-indices nil '(nil nil)) => '(0 1) + (-elem-indices t ()) => () + (-elem-indices t '(t)) => '(0) + (-elem-indices t '(nil)) => () + (-elem-indices t '(nil t)) => '(1) + (-elem-indices t '(t nil)) => '(0) + (-elem-indices t '(t t)) => '(0 1) + (-elem-indices t '(nil nil)) => ()) (defexamples -find-index - (-find-index 'even? '(2 4 1 6 3 3 5 8)) => 0 - (--find-index (< 5 it) '(2 4 1 6 3 3 5 8)) => 3 - (-find-index (-partial 'string-lessp "baz") '("bar" "foo" "baz")) => 1) + (-find-index #'numberp '(a b c)) => nil + (-find-index #'natnump '(1 0 -1)) => 0 + (--find-index (> it 5) '(2 4 1 6 3 3 5 8)) => 3 + (-find-index (-cut string< "baz" <>) '("bar" "foo" "baz")) => 1 + (--find-index nil ()) => nil + (--find-index nil '(5)) => nil + (--find-index nil '(5 6 7)) => nil + (--find-index t ()) => nil + (--find-index t '(5)) => 0 + (--find-index t '(5 . 6)) => 0 + (--find-index t '(5 6 7)) => 0 + (let (x) (--find-index (setq x it) ()) x) => nil + (let (x) (--find-index (setq x it) '(5)) x) => 5 + (let (x) (--find-index (setq x it) '(5 6 7)) x) => 5 + (let (x) (--find-index (ignore (setq x it)) ()) x) => nil + (let (x) (--find-index (ignore (setq x it)) '(5)) x) => 5 + (let (x) (--find-index (ignore (setq x it)) '(5 6 7)) x) => 7) (defexamples -find-last-index - (-find-last-index 'even? '(2 4 1 6 3 3 5 8)) => 7 - (--find-last-index (< 5 it) '(2 7 1 6 3 8 5 2)) => 5 - (-find-last-index (-partial 'string-lessp "baz") '("q" "foo" "baz")) => 1) + (-find-last-index #'numberp '(a b c)) => nil + (--find-last-index (> it 5) '(2 7 1 6 3 8 5 2)) => 5 + (-find-last-index (-partial #'string< 'a) '(c b a)) => 1 + (--find-last-index nil ()) => nil + (--find-last-index nil '(t)) => nil + (--find-last-index nil '(nil)) => nil + (--find-last-index nil '(nil nil)) => nil + (--find-last-index nil '(nil t)) => nil + (--find-last-index nil '(t nil)) => nil + (--find-last-index nil '(t t)) => nil + (--find-last-index t ()) => nil + (--find-last-index t '(t)) => 0 + (--find-last-index t '(nil)) => 0 + (--find-last-index t '(nil nil)) => 1 + (--find-last-index t '(nil t)) => 1 + (--find-last-index t '(t nil)) => 1 + (--find-last-index t '(t t)) => 1 + (--find-last-index it ()) => nil + (--find-last-index it '(t)) => 0 + (--find-last-index it '(nil)) => nil + (--find-last-index it '(nil nil)) => nil + (--find-last-index it '(nil t)) => 1 + (--find-last-index it '(t nil)) => 0 + (--find-last-index it '(t t)) => 1) (defexamples -find-indices - (-find-indices 'even? '(2 4 1 6 3 3 5 8)) => '(0 1 3 7) - (--find-indices (< 5 it) '(2 4 1 6 3 3 5 8)) => '(3 7) - (-find-indices (-partial 'string-lessp "baz") '("bar" "foo" "baz")) => '(1)) + (-find-indices #'numberp '(a b c)) => '() + (-find-indices #'numberp '(8 1 d 2 b c a 3)) => '(0 1 3 7) + (--find-indices (> it 5) '(2 4 1 6 3 3 5 8)) => '(3 7) + (--find-indices (string< "baz" it) '("bar" "foo" "baz")) => '(1) + (--find-indices nil ()) => () + (--find-indices nil '(1)) => () + (--find-indices nil '(nil)) => () + (--find-indices t ()) => () + (--find-indices t '(1)) => '(0) + (--find-indices t '(nil)) => '(0) + (--find-indices t '(1 2)) => '(0 1) + (--find-indices t '(nil nil)) => '(0 1) + (--find-indices it ()) => () + (--find-indices it '(1)) => '(0) + (--find-indices it '(nil)) => () + (--find-indices it '(1 2)) => '(0 1) + (--find-indices it '(nil nil)) => () + (-find-indices #'ignore ()) => () + (-find-indices #'ignore '(1)) => () + (-find-indices #'ignore '(nil)) => () + (-find-indices (-andfn) ()) => () + (-find-indices (-andfn) '(1)) => '(0) + (-find-indices (-andfn) '(nil)) => '(0) + (-find-indices (-andfn) '(1 2)) => '(0 1) + (-find-indices (-andfn) '(nil nil)) => '(0 1) + (-find-indices #'identity ()) => () + (-find-indices #'identity '(1)) => '(0) + (-find-indices #'identity '(nil)) => () + (-find-indices #'identity '(1 2)) => '(0 1) + (-find-indices #'identity '(nil nil)) => ()) (defexamples -grade-up (-grade-up #'< '(3 1 4 2 1 3 3)) => '(1 4 3 0 5 6 2)