Изменение n-го элемента списка - PullRequest
13 голосов
/ 19 мая 2011

Я хочу изменить 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))

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

Ответы [ 3 ]

5 голосов
/ 19 мая 2011

Как насчет

(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)))
3 голосов
/ 20 мая 2011

Это зависит от того, что вы подразумеваете под "элегантностью", но как насчет ...

(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-го элемента выполняется три раза вместо одного в рекурсивной версии (однако это не хвостовая рекурсия).

1 голос
/ 20 мая 2011

Как насчет этого:

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

С минусовой стороны это больше похоже на Алгол, чем на Лисп. Но с положительной стороны:

  • пересекает лидирующую часть списка ввода только один раз

  • он вообще не пересекает завершающую часть списка ввода

  • список вывода составляется без необходимости повторного обхода

  • результат разделяет те же концевые соты, что и исходный список (если это не нужно, измените nconc на append)

...