20

Q: how do I get the element number in a list?

nth gets element number n from a list:

(nth 2 '(a b c d))                      ; => c

I'd like to do the reverse: get the element number given the element:

(some-function 'c '(a b c d))           ; => 2

I may have missed it, but does such a function exist? How would one do this?

4 Answers 4

26
  1. Here's a function that is included with Emacs 24.3 and later:
(cl-position 2 '(6 7 8 2 3 4)) ;; => 3

(Before Emacs 24.3, use function position from library cl.el, which is included with Emacs.)

You can use the :test keyword to specify the comparison function:

(cl-position "bar" '("foo" "bar" "baz") :test 'equal) ;; => 1
(cl-position '(1 2) '((3) (5 6) (1 2) nil) :test 'equal) ;; => 2

Emacs Common Lisp Emulation Manual

  1. dash.el has a function that can do this: -elem-index
(-elem-index 2 '(6 7 8 2 3 4)) ;; => 3
(-elem-index "bar" '("foo" "bar" "baz")) ;; => 1
(-elem-index '(1 2) '((3) (5 6) (1 2) nil)) ;; => 2

It's not included with Emacs, but a lot of Emacs users already have it installed (it's a dependency of projectile, flycheck, and smartparens, which gives it a ton of coverage).

0
8

Well, if you want to roll your own instead of using cl-position, and you don't want to traverse twice (using length)...

(defun nth-elt (element xs)
  "Return zero-indexed position of ELEMENT in list XS, or nil if absent."
  (let ((idx  0))
    (catch 'nth-elt
      (dolist (x  xs)
        (when (equal element x) (throw 'nth-elt idx))
        (setq idx  (1+ idx)))
      nil)))

That's good for even old Emacs versions. However, it has this behavior difference, which you might or might not want: It works also for the cars of a dotted list. That is, it correctly returns the position instead of raising an error, for sexps such as (nth-elt 'c '(a b c . d)).

If you want to always raise an error for an improper list, then you will want to check for that case, which requires always traversing to the end of the list:

(defun nth-elt (element xs)
  "Return zero-indexed position of ELEMENT in list XS, or nil if absent."
  (let ((idx  0))
    (when (atom (cdr (last xs))) (error "Not a proper list"))
    (catch 'nth-elt
      (dolist (x  xs)
        (when (equal element x) (throw 'nth-elt idx))
        (setq idx  (1+ idx)))
      nil)))
2

Turns out it's a simple function to write, although it may not be all that efficient:

(defun nth-elt (elt list)
  "Return element number of ELT in LIST."
  (let ((loc (length (member elt list))))
    (unless (zerop loc)
      (- (length list) loc))))

(nth-elt 'c '(a b c d))                 ; => 2
(nth-elt 'f '(a b c d))                 ; => nil

I'd prefer a built-in solution if one exists, of course.

2

Another answer which uses simpler lisp-constructs than other answers here to step over the list (no throw/catch).

(defun nth-elt (element xs)
  "Return zero-indexed position of ELEMENT in list XS, or nil if absent."
  (let ((index 0))
    (while (and xs (not (equal element (car xs))))
      (setq index (1+ index))
      (setq xs (cdr xs)))
    (and xs index)))

Examples:

(nth-elt 2 '(6 7 8 2 3 4)) ;; => 3
(nth-elt "bar" '("foo" "bar" "baz")) ;; => 1
(nth-elt '(1 2) '((3) (5 6) (1 2) nil)) ;; => 2
(nth-elt 10 '(6 7 8 2 3 4)) ;; => nil

This is a version of the same function which takes an optional :test keyword argument.

This can be handy in practice when you may want to compare elements in a list or use a different comparison function.

(defun nth-elt (element xs &rest args)
  "Return zero-indexed position of ELEMENT in list XS, or nil if absent.
Optional ARGS, may contain :test keyword argument,
it's value is used instead of `equal' for comparison,
where the first argument is always ELEMENT, the second is a member of XS."
  (let ((index 0)
        (test-fn (or (plist-get args :test) #'equal)))
    (while (and xs (not (funcall test-fn element (car xs))))
      (setq index (1+ index))
      (setq xs (cdr xs)))
    (and xs index)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.