22

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?

Dan
  • 32,980
  • 7
  • 102
  • 169

4 Answers4

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).

9716278
  • 183
  • 6
nanny
  • 5,756
  • 1
  • 20
  • 38
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)))
Drew
  • 77,472
  • 10
  • 114
  • 243
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.

Stefan
  • 26,404
  • 3
  • 48
  • 85
Dan
  • 32,980
  • 7
  • 102
  • 169
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)))
ideasman42
  • 8,786
  • 1
  • 32
  • 114