Изменение n-го элемента списка
Я хочу изменить n-й элемент списка и вернуть новый список.
Я подумал о трех довольно неэлегантных решениях:
(defun set-nth1 (list n value)
(let ((list2 (copy-seq list)))
(setf (elt list2 n) value)
list2))
(defun set-nth2 (list n value)
(concatenate 'list (subseq list 0 n) (list value) (subseq list (1+ n))))
(defun set-nth3 (list n value)
(substitute value nil list
:test #'(lambda (a b) (declare (ignore a b)) t)
:start n
:count 1))
Каков наилучший способ сделать это?
Ответы
Ответ 1
Как насчет
(defun set-nth4 (list n val)
(loop for i from 0 for j in list collect (if (= i n) val j)))
Возможно, следует отметить сходство с substitute
и следовать его соглашению:
(defun substitute-nth (val n list)
(loop for i from 0 for j in list collect (if (= i n) val j)))
Кстати, в отношении set-nth3
существует функция постоянно, именно для такой ситуации:
(defun set-nth3 (list n value)
(substitute value nil list :test (constantly t) :start n :count 1))
Edit:
Другая возможность:
(defun set-nth5 (list n value)
(fill (copy-seq list) value :start n :end (1+ n)))
Ответ 2
Это зависит от того, что вы подразумеваете под "элегантностью", но как насчет...
(defun set-nth (list n val)
(if (> n 0)
(cons (car list)
(set-nth (cdr list) (1- n) val))
(cons val (cdr list))))
Если у вас есть проблемы с легкостью понимания рекурсивных определений, то небольшое отклонение nth-2 (как предложил Терье Нордерхауг) должно быть более "самоочевидным" для вас:
(defun set-nth-2bis (list n val)
(nconc (subseq list 0 n)
(cons val (nthcdr (1+ n) list))))
Единственный недостаток эффективности, который я вижу в этой версии, заключается в том, что обход до n-го элемента выполняется три раза вместо одного в рекурсивной версии (что, однако, не является рекурсивным).
Ответ 3
Как насчет этого:
(defun set-nth (list n value)
(loop
for cell on list
for i from 0
when (< i n) collect (car cell)
else collect value
and nconc (rest cell)
and do (loop-finish)
))
На минусовой стороне он больше похож на Algol, чем на Lisp. Но с положительной стороны:
-
он перемещает ведущую часть списка ввода только один раз
-
он не пересекает конечную часть входного списка вообще
-
выходной список сконфигурирован без необходимости повторного его перемещения
-
результат разделяет те же самые задние ячейки cons в качестве исходного списка (если это не желательно, измените nconc
на append
)