3

Is there a way to replace an item in a list?

(replace-element-in-list (elem-src elem-dst ls &optional times comparison-fn)) 
 ;; ...
 )

Example use: (replace-element-in-list 1 100 '(3 2 1)) => '(3 2 100)

2

3 Answers 3

1

Here is a non destructive version.

(defun replace-element-in-list (elem-src elem-dst ls &optional times comparison-fn)
  (setq times (or times (length ls)))
  (mapcar
   (lambda (item)
     (cond
      ((and (> times 0)
            (funcall (or comparison-fn #'eq) item elem-src))
       (cl-decf times)
       elem-dst)
      (t
       item)))
   ls))

(replace-element-in-list 2 "2" '(1 2 3 2 4))
;; => (1 "2" 3 "2" 4)

(replace-element-in-list 2 "2" '(1 2 3 2 4) 1)
;; => (1 "2" 3 2 4)

(replace-element-in-list "2" 2 '("1" "2" "3" "2" "4") nil #'string=)
;; => ("1" 2 "3" 2 "4")
1
  • (length ls) seems unnecessary, could use most-positive-fixnum instead, or allow it to be nil.
    – ideasman42
    Commented Sep 6, 2020 at 10:21
6
(require 'cl-lib)
(cl-substitute 100 1 '(3 2 1))

gives (3 2 100).

(cl-substitute 100 1 '(3 2 1 2 1 2 1) :count 2)

gives (3 2 100 2 100 2 1).

(cl-substitute 100 "one" '("three" "two" "one") :test #'string-equal)

gives ("three" "two" 100).

Furthermore, there are the keyword arguments :start, :end, and :from-end. See the doc.

1

The question doesn't specify what "replacement" means. Does it mean create and return a new list that is like the original one except for the indicated parts? Or does it mean replace in place, that is, modify the original list?

Here is an example that modifies the original list, i.e., a "destructive" operation.

(defun replace-element-in-list (old new xs)
  (let ((tail  (member old xs)))
    (while tail
      (setcar tail new)
      (setq tail  (member old tail))))
  xs)

(replace-element-in-list 1 100 (list 3 1 2 1)) ; => (3 100 2 100)

If you want an optional comparison-function parameter, then use (funcall comparison-function ...) instead of (member...), and default it to member.

If you want an optional times parameter then initialize a local counter to 0 and compare it to times in the while test etc.

(defun replace-element-in-list (old new xs &optional times compare-fn)
  (let ((count  0)
        tail)
    (setq compare-fn  (or compare-fn  #'member)
          tail        (funcall compare-fn old xs))
    (while (and tail  (or (not times)
                          (<= (setq count  (1+ count)) times)))
      (setcar tail new)
      (setq tail  (funcall compare-fn old tail))))
  xs)

(replace-element-in-list 1 100 (list 3 1 2 1 5 1 1 6))
; => (3 100 2 100 5 100 100 6)

(replace-element-in-list 1 100 (list 3 1 2 1 5 1 1 6) 3)
; => (3 100 2 100 5 100 1 6)

(setq x  (list 1))

(replace-element-in-list x 100 (list 3 '(1) 2 x 5 (copy-sequence x) x 6) nil #'memq)
; => (3 (1) 2 100 5 (1) 100 6)

(replace-element-in-list x 100 (list 3 '(1) 2 x 5 (copy-sequence x) x 6) 1 #'memq)
; => (3 (1) 2 100 5 (1) (1) 6)

Your Answer

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

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