我已经完成了Graham Common Lisp第5章练习5,该练习需要一个函数,该函数接受对象X和向量V,并返回V中紧接X的所有对象的列表。
它的工作原理如下:

> (preceders #\a "abracadabra")
(#\c #\d #r)
我已经完成了递归版本:
(defun preceders  (obj vec &optional (result nil) &key (startt 0))
  (let ((l (length vec)))
    (cond ((null (position obj vec :start startt :end l)) result)
          ((= (position obj vec :start startt :end l) 0)
           (preceders obj vec result
                      :startt (1+ (position obj vec :start startt :end l))))
          ((> (position obj vec :start startt :end l) 0)
           (cons (elt vec (1- (position obj vec :start startt :end l)))
                 (preceders obj vec result
                            :startt (1+ (position obj vec
                                                 :start startt
                                                 :end l))))))))
它可以正常工作,但是我的老师给了我以下批评:
“这会反复调用length。对于向量来说并不是很糟糕,但是仍然没有必要。(对于用户而言)更有效,更灵活的代码是像其他序列处理函数一样定义它。使用:start和:end关键字参数,其他方法序列函数具有相同的默认初始值。应该最多调用一次长度。”
我正在咨询Common Lisp教科书和google,但在这方面似乎没有什么帮助:我不知道他“使用:start和:end关键字参数”是什么意思,而且我不知道如何“通话长度仅一次”。如果您能给我一些想法,以改进我的代码以满足我的老师发布的要求,我将不胜感激。
更新:
现在我想出了以下代码:
(defun preceders (obj vec
                  &optional (result nil)
                  &key (start 0) (end (length vec)) (test #'eql))
  (let ((pos (position obj vec :start start :end end :test test)))
    (cond ((null pos) result)
          ((zerop pos) (preceders obj vec result
                                 :start (1+ pos) :end end :test test))
          (t (preceders obj vec (cons (elt vec (1- pos)) result)
                       :start (1+ pos) :end end :test test)))))
我得到这个批评:
“当您有一个复杂的递归调用,并且在多个分支中重复执行时,通常更容易先执行该调用,将其保存在本地变量中,然后在更简单的IF或COND中使用该变量。”
另外,对于该函数的迭代版本:
(defun preceders (obj vec)
  (do ((i 0 (1+ i))
       (r nil (if (and (eql (aref vec i) obj)
                       (> i 0))
                  (cons (aref vec (1- i)) r)
                  r)))
      ((eql i (length vec)) (reverse r))))
我受到批评
“在一个更好的点开始DO,并删除重复的> 0测试”

最佳答案

此类函数的典型参数列表为:

(defun preceders (item vector
                  &key (start 0) (end (length vector))
                       (test #'eql))
    ...
)

如您所见,它具有START和END参数。

TEST是默认的比较功能。使用(funcall测试项目(区域向量i))。
通常还有一个KEY参数...

对于PRECEDERS的每个递归调用,都会重复调用LENGTH。

我将执行非递归版本,并在向量上移动两个索引:一个用于第一项,一个用于下一项。每当下一个项目是要查找的项目的EQL时,就将第一个项目推到结果列表中(如果它不是该项目的成员)。

对于递归版本,我将编写一个由PRECEDERS调用的第二个函数,该函数接受两个以0和1开头的索引变量,并使用它。我不会调用POSITION。通常,此函数是通过PRECEDERS内部的LABELS进行的局部函数,但是为了使编写起来更容易一点,辅助函数也可以位于外部。
(defun preceders (item vector
                  &key (start 0) (end (length vector))
                       (test #'eql))
   (preceders-aux item vector start end test start (1+ start) nil))


(defun preceders-aux (item vector start end test pos0 pos1 result)
  (if (>= pos1 end)
      result
      ...
  ))

有帮助吗?

这是使用LOOP的迭代版本:
(defun preceders (item vector
                  &key (start 0) (end (length vector))
                       (test #'eql))
  (let ((result nil))
    (loop for i from (1+ start) below end
          when (funcall test item (aref vector i))
          do (pushnew (aref vector (1- i)) result))
    (nreverse result)))

10-06 16:05