Discussion:
replace element in list
e***@openmail.cc
2018-11-22 04:42:06 UTC
Permalink
Hello,

I want to share and know if there is a better way (more efficient or
clearer) to replace something within a list (I don't know LISP). My
code is like this, and it works for the given example.

(defun my-list-replace (obj orig new)
"Replaces an element in a list with something else"
(let* (
;; Position of the thing we need to remove
(pos (cl-position orig obj :test 'equal))
;; If pos is nil, reset to zero
(pos (if pos pos 0))
;; The length of the original object
(objlen (length obj))
;; The elements before the element to remove
(head (butlast obj (- objlen pos)))
;; The elements after the element to remove
(trail (nthcdr (+ 1 pos) obj)))
;; Join (1) the sub-list before the element to be replaced
;; with (2) the new element and (3) the rest of the list
(append head (append new trail))
))

;; Example simple
(my-list-replace '(("a" . "b") ("c" "d")) '("c" "d") '(":)"))
;; (("a" . "b") ":)")

(my-list-replace '(("a" . "b") ("c" "d")) '("c" "d") (list '("hi")))
;; (("a" . "b") ("hi"))


;; Example long
(setq org-latex-default-packages-alist
(my-list-replace org-latex-default-packages-alist
'("T1" "fontenc" t ("pdflatex"))
(list
'("" "unicode-math" t ("xelatex" "xetex"))
'("" "lmodern" t ("pdflatex"))
'("QX" "fontenc" t ("pdflatex")))))

-------------------------------------------------

ONLY AT VFEmail! - Use our Metadata Mitigator to keep your email out of the NSA's hands!
$24.95 ONETIME Lifetime accounts with Privacy Features!
15GB disk! No bandwidth quotas!
Commercial and Bulk Mail Options!
Eric Abrahamsen
2018-11-22 05:18:22 UTC
Permalink
Post by e***@openmail.cc
Hello,
I want to share and know if there is a better way (more efficient or
clearer) to replace something within a list (I don't know LISP). My
code is like this, and it works for the given example.
(defun my-list-replace (obj orig new)
"Replaces an element in a list with something else"
(let* (
;; Position of the thing we need to remove
(pos (cl-position orig obj :test 'equal))
;; If pos is nil, reset to zero
(pos (if pos pos 0))
;; The length of the original object
(objlen (length obj))
;; The elements before the element to remove
(head (butlast obj (- objlen pos)))
;; The elements after the element to remove
(trail (nthcdr (+ 1 pos) obj)))
;; Join (1) the sub-list before the element to be replaced
;; with (2) the new element and (3) the rest of the list
(append head (append new trail))
))
You'll probably get a bunch of suggestions, but mine is to use `setf'
and `nth'. You can do:

(let ((orig '(("a" . "b") ("c" "d")))
(obj '("c" "d"))
(new '(":)")))
(setf (nth (cl-position obj orig :test #'equal) orig) new)
orig)

Hope that's useful.

Eric
Drew Adams
2018-11-22 05:39:11 UTC
Permalink
Post by Eric Abrahamsen
Post by e***@openmail.cc
(defun my-list-replace (obj orig new)
"Replaces an element in a list with something else"
(let* ((pos (cl-position orig obj :test 'equal))
(pos (if pos pos 0))
(objlen (length obj))
(head (butlast obj (- objlen pos)))
(trail (nthcdr (+ 1 pos) obj)))
(append head (append new trail))))
(let ((orig '(("a" . "b") ("c" "d")))
(obj '("c" "d"))
(new '(":)")))
(setf (nth (cl-position obj orig :test #'equal) orig) new)
orig)
(defun toto (xs old new)
(let ((ms (member old xs)))
(unless ms (error "%S is not in %S" old xs))
(setcar ms new)
xs))
Eric Abrahamsen
2018-11-22 06:04:35 UTC
Permalink
Post by Drew Adams
Post by Eric Abrahamsen
Post by e***@openmail.cc
(defun my-list-replace (obj orig new)
"Replaces an element in a list with something else"
(let* ((pos (cl-position orig obj :test 'equal))
(pos (if pos pos 0))
(objlen (length obj))
(head (butlast obj (- objlen pos)))
(trail (nthcdr (+ 1 pos) obj)))
(append head (append new trail))))
(let ((orig '(("a" . "b") ("c" "d")))
(obj '("c" "d"))
(new '(":)")))
(setf (nth (cl-position obj orig :test #'equal) orig) new)
orig)
(defun toto (xs old new)
(let ((ms (member old xs)))
(unless ms (error "%S is not in %S" old xs))
(setcar ms new)
xs))
Old school, and proper :)
Stefan Monnier
2018-11-22 13:19:49 UTC
Permalink
Post by e***@openmail.cc
I want to share and know if there is a better way (more efficient or
clearer) to replace something within a list (I don't know LISP).
My suggestion is to not do it:
- if you do it by modifying the list in place, it means you're using
nasty side-effects, which are better avoided when possible
(especially with lists).
- if you want to do it without side-effects, your operation will
inevitably be algorithmically inefficient because a list is not
designed for that.


-- Stefan
Robert Munyer
2018-11-23 12:05:16 UTC
Permalink
Post by Stefan Monnier
Post by e***@openmail.cc
(defun my-list-replace (obj orig new)
"Replaces an element in a list with something else"
[...]
Post by Stefan Monnier
Post by e***@openmail.cc
(my-list-replace '(("a" . "b") ("c" "d")) '("c" "d") (list '("hi")))
;; (("a" . "b") ("hi"))
If nothing in the list matches your "orig" item, your function will
replace the _first_ item. Did you intend that?

(my-list-replace '(("a" . "b") ("c" "d")) '("e" "f") (list '("hi")))
;; (("hi") ("c" "d"))
Post by Stefan Monnier
- if you do it by modifying the list in place, it means you're using
nasty side-effects, which are better avoided when possible
(especially with lists).
Good point.
Post by Stefan Monnier
- if you want to do it without side-effects, your operation will
inevitably be algorithmically inefficient because a list is not
designed for that.
If he doesn't want to run it very frequently nor on very long lists,
moderate inefficiency is OK.

Edgar, here is one that avoids the nasty side-effects that Stefan
mentioned. It isn't especially efficient, but it is simple and clear.
Warning: it behaves the same as your original version _only_ if there
is exactly one matching item.

(defun my-list-replace-2 (l old-item new-items)
(apply 'append
(mapcar (lambda (x)
(cond ((equal x old-item) new-items)
(t (list x))))
l)))
--
Robert Munyer
E-mail: (reverse (append '(com dot munyer at) (list (* 91837 99713))))
e***@openmail.cc
2018-11-24 03:01:18 UTC
Permalink
From Eric
Post by Eric Abrahamsen
You'll probably get a bunch of suggestions, but mine is to use `setf'
(let ((orig '(("a" . "b") ("c" "d")))
(obj '("c" "d"))
(new '(":)")))
(setf (nth (cl-position obj orig :test #'equal) orig) new)
orig)
Hope that's useful.
Eric
Thank you, Eric.


From Drew
Post by Eric Abrahamsen
(defun toto (xs old new)
(let ((ms (member old xs)))
(unless ms (error "%S is not in %S" old xs))
(setcar ms new)
xs))
Thanks (I wonder why toto :P ).

From Stefan
Post by Eric Abrahamsen
- if you do it by modifying the list in place, it means you're using
nasty side-effects, which are better avoided when possible
(especially with lists).
- if you want to do it without side-effects, your operation will
inevitably be algorithmically inefficient because a list is not
designed for that.
-- Stefan
Thank you.


From Robert
Post by Eric Abrahamsen
Edgar, here is one that avoids the nasty side-effects that Stefan
mentioned. It isn't especially efficient, but it is simple and clear.
Warning: it behaves the same as your original version _only_ if there
is exactly one matching item.
(defun my-list-replace-2 (l old-item new-items)
(apply 'append
(mapcar (lambda (x)
(cond ((equal x old-item) new-items)
(t (list x))))
l)))
Thanks! This is great :). I didn't know that the first element would be
replaced :S !!


People, you are truly great. Thank you all.

-------------------------------------------------

ONLY AT VFEmail! - Use our Metadata Mitigator to keep your email out of the NSA's hands!
$24.95 ONETIME Lifetime accounts with Privacy Features!
15GB disk! No bandwidth quotas!
Commercial and Bulk Mail Options!
Drew Adams
2018-11-24 05:27:09 UTC
Permalink
Post by e***@openmail.cc
(defun toto (xs old new)...
Thanks (I wonder why toto :P ).
No special reason. toto, titi, tata, tutu, foo, bar, baz,...
e***@openmail.cc
2018-11-24 07:22:47 UTC
Permalink
Post by Drew Adams
Post by e***@openmail.cc
(defun toto (xs old new)...
Thanks (I wonder why toto :P ).
No special reason. toto, titi, tata, tutu, foo, bar, baz,...
I thought it was a long joke related to the Wizard of Oz.

-------------------------------------------------

ONLY AT VFEmail! - Use our Metadata Mitigator to keep your email out of the NSA's hands!
$24.95 ONETIME Lifetime accounts with Privacy Features!
15GB disk! No bandwidth quotas!
Commercial and Bulk Mail Options!

Loading...