Common Lisp中最大的子列表

时间:2014-06-11 14:55:11

标签: lisp common-lisp

我正在尝试使用Common Lisp从列表中获取最大的子列表。

(defun maxlist (list)
(setq maxlen (loop for x in list maximize (list-length x)))
(loop for x in list (when (equalp maxlen (list-length x)) (return-from maxlist x)))
)

我们的想法是遍历列表两次:第一个循环获取最大子列表的大小,第二个循环获取所需的列表。但由于某些原因,我一直在return-from行中收到错误。我错过了什么?

2 个答案:

答案 0 :(得分:6)

loop

的主要问题

这里有一些问题。首先,您可以按如下方式编写循环。 Common Lisp中有return-fromwhile个表单,但loop定义了自己的小语言,也识别whilereturn,所以你可以使用那些:

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

list-lengthlength非常相似,只是如果列表具有循环结构,则返回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没有明显优雅的解决方案,但这里的实现基于reduceloopdo*

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

答案 1 :(得分:3)

短变种

CL-USER> (defparameter *test* '((1 2 3) (4 5) (6 7 8 9)))
*TEST*
CL-USER> (car (sort *test* '> :key #'length))
(6 7 8 9)

保罗格雷厄姆的most

请考虑保罗格雷厄姆的most功能:

(defun most (fn lst)
  (if (null lst)
      (values nil nil)
      (let* ((wins (car lst))
             (max (funcall fn wins)))
        (dolist (obj (cdr lst))
          (let ((score (funcall fn obj)))
            (when (> score max)
              (setq wins obj
                    max score))))
        (values wins max))))

这是测试的结果(它还返回由提供的函数返回的最佳&#39;元素的值):

CL-USER> (most #'length *test*)
(6 7 8 9)
4

extreme实用程序

过了一会儿,我想出了extreme效用,部分基于保罗格雷厄姆的功能。这是有效且非常普遍的:

(declaim (inline use-key))
(defun use-key (key arg)
  (if key (funcall key arg) arg))

(defun extreme (fn lst &key key)
  (let* ((win (car lst))
         (rec (use-key key win)))
    (dolist (obj (cdr lst))
      (let ((test (use-key key obj)))
        (when (funcall fn test rec)
          (setq win obj rec test))))
    (values win rec)))

它需要比较谓词fn,元素列表和(可选)key参数。可以很容易地找到具有指定质量极值的对象:

CL-USER> (extreme #'> '(4 9 2 1 5 6))
9
9
CL-USER> (extreme #'< '(4 9 2 1 5 6))
1
1
CL-USER> (extreme #'> '((1 2 3) (4 5) (6 7 8 9)) :key #'length)
(6 7 8 9)
4
CL-USER> (extreme #'> '((1 2 3) (4 5) (6 7 8 9)) :key #'cadr)
(6 7 8 9)
7

请注意,这件事在亚历山大里亚被称为extremum。它也可以用于序列。