仅使用构造函数中的列表查找 Lisp 列表中的最小数字?

Posted

技术标签:

【中文标题】仅使用构造函数中的列表查找 Lisp 列表中的最小数字?【英文标题】:Finding the smallest number in a list in Lisp using only a list in the constructor? 【发布时间】:2018-05-30 18:08:37 【问题描述】:

我的教授在期中考试时给了我们这个难题,除非我看的不对,否则这是一个看起来很简单的相当棘手的问题。

    编写一个 lisp 函数 f 来查找列表的最小值。假设列表仅包含数字。例如 (f '(3 2 5 4 9 5)) 返回 2。

这是我目前所拥有的:

(defun f (L)
 (cond ((null (cdr L))(car L))    ; <-- I think my break case is wrong, too.
       ((< (car L) (car (cdr L))) (rotatef((car L) (car(cdr L)))))
 (f(cdr L))
 )
)

编译器告诉我,我对 rotatef 的使用不好。

我的逻辑是不断与car(cdr L)交换最小元素,始终以car(L)为最小元素。然后递归调用cdr,直到只有那是我能想到的唯一方法。奇怪的是他从未在我们的笔记中提到rotatef,所以我认为我做的很糟糕。

伟大的 Lisp 大神,你能帮帮我吗?你是我唯一的希望。

【问题讨论】:

Lisp, instructions not working in defun的可能重复 【参考方案1】:

不确定它是否是您喜欢的解决方案,但它是一个有效的解决方案:

(defun smallest (list current_small test)
    (cond ((null list) current_small)
          ((funcall test current_small (first list))
            (smallest (rest list) (first list) test))
            (t (smallest (rest list) current_small test))))

(defun check_smallest (current_small current_value)
    (if(> current_small current_value) current_value nil) 
)     
(defparameter mylist (list 3 33 2 1 32 87 -1 -222 45))

(print (smallest (rest mylist) (first mylist) #'check_smallest))

【讨论】:

【参考方案2】:

(defun F (L) (应用'min L) )

【讨论】:

答案通常最好有一些解释,而不仅仅是一行代码。我不知道 LISP,所以无法评论准确性。【参考方案3】:

这确实是对 Rainer Joswig 漂亮答案的扩展评论。

他的minimum 函数所做的本质上是将列表的第一个元素视为运行中的最佳猜测值,反复构建一个新的、更短的列表,并将这个最佳猜测值作为其第一个元素。这真的很聪明,因为寻找最小值的整个算法确实是这样工作的:猜测第一个数字,将其与第二个数字进行比较,然后适当地递归。

解决此类问题的另一种方法是将此运行最佳猜测作为要计算最小值的函数的不同参数。当像这样使用时,这个替代参数有时被称为“累加器”,因为在其他相关模式中它用于累加结果。这种模式只有在你愿意定义一个辅助函数来完成这项工作时才真正起作用,因为你通常不希望***函数有这个额外的参数。

所以,Rainer 的函数改写如下:

(defun minimum/tfb (list)
   "function to return the minimum value of a list of numbers, via a recursive helper"
   (when (null list)
     ;; I claim that the empty list is an error, so let's check this
     ;; and say so
     (error "empty lists don't have minima"))
   (labels ((minimum-loop (tail min-so-far)
              ;; TAIL is everything else we need to look at,
              ;; MIN-SO-FAR is the running minimum
              (cond ((null tail)
                     ;; we are done: the running minimum is the minimum
                     min-so-far)
                    ((< min-so-far (first tail))
                     ;; the running minimum is smaller than the first
                     ;; element of TAIL so it is still our best bet:
                     ;; recurse on the rest of TAIL
                     (minimum-loop (rest tail) min-so-far))
                    (t
                     ;; the first element of TAIL is less than or
                     ;; equal to min-so-far, so it is now our best bet
                     (minimum-loop (rest tail) (first tail))))))
     ;; Now start the process (remember we know LIST has at least one
     ;; element, which we use as out best guess).
     (minimum-loop (rest list) (first list))))

很容易看出这与 Rainer 的函数相同(除了如果给出的列表为空,它会发出错误信号,这是一个见仁见智的问题),但它使用了这个本地 minimum-loop 函数工作,并有这个额外的论点。

通过观察你可以在调用点计算额外的参数,可以以一种更无偿的功能方式重写它:

(defun minimum/tfb/gratuitous (list)
   "function to return the minimum value of a list of numbers, via a recursive helper"
   (when (null list)
     ;; I claim that the empty list is an error, so let's check this
     ;; and say so
     (error "empty lists don't have minima"))
   (labels ((minimum-loop (tail min-so-far)
              ;; TAIL is everything else we need to look at,
              ;; MIN-SO-FAR is the running minimum
              (if (null tail)
                  ;; we are done: the running minimum is the minimum
                  min-so-far
                ;; We have more to do: recurse, deciding which element
                ;; to use as the running minimum
                (minimum-loop (rest tail)
                              (if (< min-so-far (first tail))
                                  min-so-far (first tail))))))
     ;; Now start the process (remember we know LIST has at least one
     ;; element, which we use as out best guess).
     (minimum-loop (rest list) (first list))))

我不认为这实际上在风格上更好,而且可能更糟。然而,这可能是我要写的。 (Python 程序员讨厌我的代码。)

最后请注意,Rainer 的函数和我对它的修改都是尾递归的。在像 Scheme 这样的语言中,这些函数将对应于明确的迭代过程。实际上,在 Scheme 中,迭代构造是根据尾调用定义的。对于这种辅助函数模式,Scheme 也有很好的语法。

这是我的第二个函数到 Scheme 系列语言(Racket)的简单音译:

(define (minimum lyst)
  (when (null? lyst)
    (error "empty lists do not have minima"))
  (define (minimum-loop tail min-so-far)
    (if (null? tail)
        min-so-far
        (minimum-loop (rest tail)
                      (if (< min-so-far (first tail))
                          min-so-far
                          (first tail)))))
  (minimum-loop (rest lyst) (first lyst)))

(请注意 list 的烦人拼写为 lyst,因为 Scheme 是 Lisp-1,并注意内部 define 的工作方式类似于 Scheme 中的 labels。)

这里是使用named-let的转换,其目的是支持这种事情:

(define (minimum/loop lyst)
  (when (null? lyst)
    (error "empty lists do not have minima"))
  (let minimum-loop ([tail (rest lyst)]
                     [min-so-far (first lyst)])
    (if (null? tail)
        min-so-far
        (minimum-loop (rest tail)
                      (if (< min-so-far (first tail))
                          min-so-far
                          (first tail))))))

【讨论】:

【参考方案4】: 你应该计算一个最小值,所以让我们调用函数minimum L 作为变量名可以替换为list 改进缩进和括号

然后它看起来像这样:

(defun minimum (list)
 (cond ((null (cdr list))
        (car list))    ; <-- I think my break case is wrong, too.
       ((< (car list) (car (cdr list)))
        (rotatef ((car list)
                  (car (cdr list)))))
       (minimum
        (cdr list))))

现在你可以摆脱CARCDR

(defun minimum (list)
 (cond ((null (rest list))
        (first list))    ; <-- I think my break case is wrong, too.
       ((< (first list) (second list))
        (rotatef ((first list)
                  (second list))))
       (minimum
        (rest list))))

最后一个 cond 子句中的最小值没有意义,因为它需要一个带有布尔值的列表:

(defun minimum (list)
 (cond ((null (rest list))
        (first list))    ; <-- I think my break case is wrong, too.
       ((< (first list) (second list))
        (rotatef ((first list)
                  (second list))))
       (t (minimum (rest list)))))

ROTATEF 接受多少个参数? ((foo) (bar)) 在 Lisp 中没有用处,因为你不能就这样在一堆 Lisp 表单周围加上括号。

(defun minimum (list)
 (cond ((null (rest list))
        (first list))    ; <-- I think my break case is wrong, too.
       ((< (first list) (second list))
        (rotatef (first list)
                 (second list)))
       (t
        (minimum (rest list)))))

空列表是怎么回事?

(defun minimum (list)
 (cond ((null list)
        nil)
       ((null (rest list))
        (first list))
       ((< (first list) (second list))
        (rotatef (first list)
                 (second list)))
       (t
        (minimum (rest list)))))

ROTATEF 没有意义,因为它不返回最小值。

(defun minimum (list)
 (cond ((null list)
        nil)
       ((null (rest list))
        (first list))
       ((< (first list) (second list))
        (minimum (cons (first list)
                       (rest (rest list)))))
       (t
        (minimum (rest list)))))

最后添加一个简短的文档字符串。

(defun minimum (list)
 "recursive function to return the minimum value of a list of numbers"
 (cond ((null list)
        nil)
       ((null (rest list))
        (first list))
       ((< (first list) (second list))
        (minimum (cons (first list)
                       (rest (rest list)))))
       (t
        (minimum (rest list)))))

解释:

(defun minimum (list)

 "recursive function to return the minimum value of a list of numbers"

 (cond

       ((null list)                      ; list is empty
        nil)

       ((null (rest list))               ; only one element
        (first list))

       ((< (first list) (second list))   ; if first element is smaller than second
        (minimum (cons (first list)      ; call minimum without second element 
                       (rest (rest list)))))

       (t                                ; second is equal or smaller
        (minimum (rest list)))))         ; call minimum without first element

现在我们也必须对其进行测试:

CL-USER 24 > (let ((lists '(()
                            (1)
                            (1 2)
                            (2 1)
                            (3 2 1 0)
                            (1 2 3)
                            (3 4 2 1 1)
                            (1 1 12 -1 4 2))))
               (mapcar #'minimum lists))

(NIL 1 1 1 0 1 1 -1)

【讨论】:

【参考方案5】:

回到基础。

您的函数可以接收的最短列表是什么?空列表。我们应该为此付出什么?目前还不清楚。让我们返回nil

您的函数可以接收的下一个最短列表是什么?包含单个数字的列表。那应该得到什么回报?我们应该返回那个单一的数字。

否则,我们有一个至少包含两个数字的列表。有(car L)(列表中的第一个数字),还有(cdr L)(列表中的所有其余数字)。如果(car L) 小于(cdr L) 中的最小数字,那么我们应该返回(car L)。否则,我们应该从(cdr L) 返回那个最小的数字。

现在,我们如何从(cdr L) 中得到最小的数字?好吧,我们已经在编写执行此操作的函数了!我们可以调用(f (cdr L))获取(cdr L)中的最小数字。

(defun f (L)
    (cond
        ; empty list
        ((null (car L)) nil)

        ; list with just one element
        ((null (cdr L)) (car L))

        ; 2 or more elements
        (T (let ((head (car L))
                 (tailMin (f (cdr L))))
                (if (< head tailMin) head tailMin)))))

【讨论】:

【参考方案6】:

Parens are meaningful in lisp: 这就是问题所在 与您的rotatef

您还需要使用funcall: 而不是(f ...)(funcall f ...),这是lisp-1 vs lisp-2 问题。

【讨论】:

以上是关于仅使用构造函数中的列表查找 Lisp 列表中的最小数字?的主要内容,如果未能解决你的问题,请参考以下文章

emacs lisp中关联列表中的匹配键

解析 Common Lisp 列表中的符号

在 common lisp 中复制结构列表

查找列表中的最小元素(递归) - Python

使用 DataTemplate 时,ListView 仅显示列表中的最后一项

python 怎么取列表中最小的数