Подтвердить что ты не робот

Изменение 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))

Каков наилучший способ сделать это?

4b9b3361

Ответ 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)