loop 的主要问题
这里有一些问题。首先,您可以将循环编写如下。 Common Lisp 中有 return-from 和 while 形式,但 loop 定义了自己的小语言,也可以识别 while 和 return,所以你可以使用它们:
(loop for x in list
when (equalp maxlen (list-length x))
return x)
这样的循环实际上可以用find 更简洁地编写。这只是
(find maxlen list :key list-length :test 'equalp)
但是请注意,list-length 应该总是返回一个数字或nil,所以equalp 是多余的。你可以只使用eql,这是find的默认值,所以你甚至可以写
(find maxlen list :key list-length)
list-length 和 maximize
list-length 很像length,除了如果列表具有循环结构,则返回nil,而使用不正确的列表调用length 是错误的。但是,如果您使用(loop ... maximize ...),则不能有nil 值,因此list-length 处理length 的唯一情况不会是仍然会给您错误的情况。例如,
CL-USER> (loop for x in '(4 3 nil) maximize x)
; Evaluation aborted on #<TYPE-ERROR expected-type: REAL datum: NIL>.
(实际上,length 也适用于其他类型的序列,因此如果您传递向量,list-length 会出错,但 length 不会。)所以,如果您知道它们都是正确的列表,你可以
(loop for x in list
maximizing (length x))
如果它们不一定都是正确的列表(所以你确实需要list-length),那么你需要像这样保护:
(loop for x in list
for len = (list-length x)
unless (null len) maximize len)
更高效的 argmax
但是,现在您要对列表进行两次传递,并且要计算每个子列表的长度两次。一次是你计算最大长度的时候,另一个是你去找到一个最大值的时候。如果您一次性完成此操作,您将节省时间。 argmax 没有明显的优雅解决方案,但这里有基于 reduce、loop 和 do* 的实现。
(defun argmax (fn list &key (predicate '>) (key 'identity))
(destructuring-bind (first &rest rest) list
(car (reduce (lambda (maxxv x)
(destructuring-bind (maxx . maxv) maxxv
(declare (ignore maxx))
(let ((v (funcall fn (funcall key x))))
(if (funcall predicate v maxv)
(cons x v)
maxxv))))
rest
:initial-value (cons first (funcall fn (funcall key first)))))))
(defun argmax (function list &key (predicate '>) (key 'identity))
(loop
for x in list
for v = (funcall function (funcall key x))
for maxx = x then maxx
for maxv = v then maxv
when (funcall predicate v maxv)
do (setq maxx x
maxv v)
finally (return maxx)))
(defun argmax (function list &key (predicate '>) (key 'identity))
(do* ((x (pop list)
(pop list))
(v (funcall function (funcall key x))
(funcall function (funcall key x)))
(maxx x)
(maxv v))
((endp list) maxx)
(when (funcall predicate v maxv)
(setq maxx x
maxv v))))
它们产生相同的结果:
CL-USER> (argmax 'length '((1 2 3) (4 5) (6 7 8 9)))
(6 7 8 9)
CL-USER> (argmax 'length '((1 2 3) (6 7 8 9) (4 5)))
(6 7 8 9)
CL-USER> (argmax 'length '((6 7 8 9) (1 2 3) (4 5)))
(6 7 8 9)