【问题标题】:Largest sublist in Common LispCommon Lisp 中最大的子列表
【发布时间】:2014-06-11 14:55:10
【问题描述】:

我正在尝试使用 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 行中不断收到错误消息。我错过了什么?

【问题讨论】:

  • 你遇到了什么错误?
  • @FrédéricHamidi 语法错误

标签: lisp common-lisp


【解决方案1】:

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-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 没有明显的优雅解决方案,但这里有基于 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)

【讨论】:

  • 感谢您的全面回答
【解决方案2】:

短变体

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

请考虑一下 Paul Graham 的 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))))

这是测试的结果(它还返回由提供的函数为“最佳”元素返回的值):

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

extreme 实用程序

一段时间后,我想出了extreme 实用程序的想法,部分基于 Paul Graham 的函数。它高效且非常通用:

(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。它也可以处理序列。

【讨论】:

  • 但是当只通过一次就足够时,为什么要对整个列表进行排序呢?另外,这个在每个子列表上调用length 多少次? sort 采用 :key 参数是有原因的。如果你确实使用这种方法,它应该只是(sort *test* '&gt; :key 'length)。但是,即使在那里,也无法保证调用密钥的次数。
  • @Joshua,你说得对,我只是想提出一些简短的建议。
  • 简洁明了的代码绝对有优势。对于短期情况,这还不错,但请务必使用可用的设施。 (sort list '&gt; :key 'length) 是相同的方法,但代码更短。 :)
  • @Joshua,是的,我已经更正了第一个变体。这只是我想到的第一件事,你知道 ;-)
【解决方案3】:

使用递归:

(defun maxim-list (l)
  (flet ((max-list (a b) (if (> (length a) (length b)) a b)))
    (if (null l)
    nil
    (max-list (car l) (maxim-list (cdr l))))))

max-list 内部函数获取两个列表中最长的一个。 maxim-list 是第一个列表中最长的,而 maxim-list 是其余列表中最长的。

【讨论】:

  • 此代码不使用尾递归。 maxim-list 的递归调用不是函数体中的最后一次调用。还要考虑格式化并将辅助函数max-list 移动到主函数体中(例如使用flet)。
猜你喜欢
  • 1970-01-01
  • 2013-05-16
  • 1970-01-01
  • 2011-03-31
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 1970-01-01
  • 2011-05-12
相关资源
最近更新 更多