不知道为什么let函数不能正确返回sbcl lisp

时间:2018-08-15 00:02:32

标签: recursion lisp common-lisp sbcl

因此,我编写了一个函数,用于按无序对的“值”(对的第二部分)对列表进行排序。这是我对递归函数的尝试(我知道它具有基本的设计):

*编辑功能设计:该功能的工作依据:

  1. 获取无序对列表,排序频率列表以及用于递归调用的可选startList。首先将listRet设置为等于startList。

  2. 如果无序列表仅包含一个元素,则将该元素推入listRet

  3. 如果该列表大于1,那么将遍历每对不规则列表,并检查其是否等于有序频率列表的第一个元素。

  4. 如果不是最后一个元素,则将其推送到listRet。

  5. 然后,循环继续进行,直到命中最后一个元素为止,然后以递归方式调用该函数,并从无序列表中删除被推入的对,因为已将其正确放置在listRet和最高频率中。 listRet作为可选的startList参数放置在适当的位置。

  6. 现在,如果无序列表包含多个元素,而最后一个元素是正确的排序频率,那么我选择将该元素移到列表的最前面并进行递归调用。

  7. 现在可以拉出无序列表的第一个元素,然后滥用do循环到达列表的末尾,并再次执行步骤5中的递归函数调用。

  8. 首先,如果无序列表的长度为1(如第2步),则退出if语句,并应返回listRet

功能:

(defun sort-by-freq (listUnord listOrdFreqs &optional startList)
  (print startList)
  (let ((listRet startList)) ;; step 1
     (if (equal (length listUnord) 1) ;;step 2
         ;;(print listRet)
         (push (car listUnord) listRet)
         (loop for pair in listUnord ;;step 3
           do (if (and (equal (cdr pair) (car listOrdFreqs)) 
                       (not (equal (last-element-by-pair listUnord)(car pair))))
                  (push pair listRet) ;;step 4
                  (if (and (equal (cdr pair) (car listOrdFreqs))
                           (equal (last-element-by-pair listUnord)(car pair))) 
                      (sort-by-freq (append (list (cons (car pair) (cdr pair)))
                                            (remove-element listUnord pair)) 
                                    listOrdFreqs listRet) ;;step 6
                     (if (equal (last-element-by-pair listUnord)(car pair))
                         (sort-by-freq (remove-element listUnord (car listRet))
                                       (cdr listOrdFreqs)
                                       listRet)))))) ;; step 5
  listRet)) ;;step 8

所以,如果我打电话给我

(sort-by-freq (list(cons 'c 2)(cons 'b 3)(cons 'a 1)) '(3 2 1))

我希望得到结果:

((A . 1) (C . 2) (B . 3)) 

但是由于某种原因,我只得到回报:

((B . 3)) 

使用(print startList)语句,我可以确认startList正在按照我的期望进行构建。它输出:

NIL 
((B . 3)) 
((B . 3)) 
((C . 2) (B . 3)) 

我已经通过注释掉的;;(print retList)确认了在输出((C . 2) (B . 3))之后达到了退出条件。 (push (car listUnord) listRet)应该将第三个元素(A . 1)推到列表的最前面,并返回listRet。这与我使用输出设计其他功能的方式一致并且有效。

我想念什么?

*编辑这是我使用的两个辅助函数:

(defun remove-element (origPairList origPair)
  (let ((retlist (list)))
    (loop for pair in origPairList
      do (if (not(equal(car origPair)(car pair)))
           (push pair retlist)))
  retlist))

(defun last-element-by-pair (pairList)
  (let ((lastEl (car (car pairList))))
    (if (not (null (cdr pairList)))
      (loop for el in pairList
         do (setf lastEl (car el))))
  lastEl))

3 个答案:

答案 0 :(得分:2)

一些提示...

简化事情

(defun remove-element (origPairList origPair)
  (let ((retlist (list)))
    (loop for pair in origPairList
      do (if (not(equal(car origPair)(car pair)))
           (push pair retlist)))
  retlist))

(defun remove-element (list pair)
  (remove (car pair) list :key #'car))

(defun last-element-by-pair (pairList)
  (let ((lastEl (car (car pairList))))
    (if (not (null (cdr pairList)))
      (loop for el in pairList
         do (setf lastEl (car el))))
  lastEl))

(defun last-element-by-pair (pair-list)
  (caar (last pair-list)))

糟糕的汽车/ CDR混乱。在LOOP中使用解构。

(loop for pair in foo ... (car pair) ... (cdr pair) ... (car pair) ...)

(loop for (head . tail) in foo ... head ... tail ... head ...)

不要一直走着走

然后

(if (equal (length foo) 1) ...)

(if (and (consp foo) (rest foo)) ...)  ; no need to traverse the list

其他问题:

您还需要确保代码正确缩进。通常,在编辑器中通过键盘输入即可。另外,您的代码缺少右括号。因此,该代码在语法上是不正确的。

(defun sort-by-freq (listUnord listOrdFreqs &optional startList)
  (print startList)
  (let ((listRet startList))
    (if (equal (length listUnord) 1)
        (push (car listUnord) listRet) ;; <- this makes no sense.
                                       ;; since you quit the function
                                       ;; there is no useful effect
      (loop for pair in listUnord
            do (if (and (equal (cdr pair) (car listOrdFreqs)) 
                        (not (equal (last-element-by-pair listUnord)(car pair) ))) 
                   (push pair listRet)
                 (if (and (equal (cdr pair) (car listOrdFreqs)) 
                          (equal (last-element-by-pair listUnord)(car pair)))
                     ;; this call to sort-by-freq makes no sense.
                     ;; you are not using the return value 
                     (sort-by-freq ;; what are you appending here, only one list?
                                   (append (list (cons (car pair) (cdr pair))
                                                 (remove-element listUnord pair)))
                                   listOrdFreqs
                                   listRet)
                   (if (equal (last-element-by-pair listUnord)(car pair))
                       ;; this call to sort-by-freq makes no sense.
                       ;; you are not using the return value 
                       (sort-by-freq (remove-element listUnord (car listRet))
                                     (cdr listOrdFreqs)
                                     listRet))))))
    listRet))

基本上在

(loop for e in list
      do (compute-some-thing-and-return-it e))

调用该函数没有任何意义,因为未使用返回值。调用该函数的唯一原因是它具有副作用。

示例:

CL-USER 310 > (loop for e in '(1 2 3 4)
                    do (if (evenp e)
                           (* e 10)
                         (* e 100)))
NIL

如您所见,它返回NIL。可能不是您想要的。

答案 1 :(得分:2)

主要问题

您正在do子句中的循环内评估表达式。的 返回值永远不会在任何地方使用。

函数的返回值由局部变量listRet给出, 设置在函数的开头,并在 2个地方,两个调用push。第一个只发生在 输入大小为1的列表。第二次推送仅发生在步骤4。

我们可以很容易地看到所有其他操作均无效 在本地listRet变量上。另外,因为sort-by-freq 因为您的辅助功能是纯净的(它们永远不会破坏 listRet指向的列表结构),您还知道列表是 不会因为其缺点细胞的连接方式不同而被修改 随着时间的流逝。

让我们通过使用示例跟踪代码来确认这一点:

(trace sort-by-freq last-element-by-pair remove-element)

当您评估测试时,会发出以下跟踪信息(输出因实现而异,此处使用的是SBCL):

0: (SORT-BY-FREQ ((C . 2) (B . 3) (A . 1)) (3 2 1))
  1: (LAST-ELEMENT-BY-PAIR ((C . 2) (B . 3) (A . 1)))
  1: LAST-ELEMENT-BY-PAIR returned A
  1: (LAST-ELEMENT-BY-PAIR ((C . 2) (B . 3) (A . 1)))
  1: LAST-ELEMENT-BY-PAIR returned A
  1: (LAST-ELEMENT-BY-PAIR ((C . 2) (B . 3) (A . 1)))
  1: LAST-ELEMENT-BY-PAIR returned A
  1: (REMOVE-ELEMENT ((C . 2) (B . 3) (A . 1)) (B . 3))
  1: REMOVE-ELEMENT returned ((A . 1) (C . 2))
  1: (SORT-BY-FREQ ((A . 1) (C . 2)) (2 1) ((B . 3)))

    2: (LAST-ELEMENT-BY-PAIR ((A . 1) (C . 2)))
    2: LAST-ELEMENT-BY-PAIR returned C
    2: (LAST-ELEMENT-BY-PAIR ((A . 1) (C . 2)))
    2: LAST-ELEMENT-BY-PAIR returned C
    2: (LAST-ELEMENT-BY-PAIR ((A . 1) (C . 2)))
    2: LAST-ELEMENT-BY-PAIR returned C
    2: (REMOVE-ELEMENT ((A . 1) (C . 2)) (C . 2))
    2: REMOVE-ELEMENT returned ((A . 1))
    2: (SORT-BY-FREQ ((C . 2) (A . 1)) (2 1) ((B . 3)))

      3: (LAST-ELEMENT-BY-PAIR ((C . 2) (A . 1)))
      3: LAST-ELEMENT-BY-PAIR returned A
      3: (LAST-ELEMENT-BY-PAIR ((C . 2) (A . 1)))
      3: LAST-ELEMENT-BY-PAIR returned A
      3: (REMOVE-ELEMENT ((C . 2) (A . 1)) (C . 2))
      3: REMOVE-ELEMENT returned ((A . 1))
      3: (SORT-BY-FREQ ((A . 1)) (1) ((C . 2) (B . 3)))

      3: SORT-BY-FREQ returned ((A . 1) (C . 2) (B . 3))
    2: SORT-BY-FREQ returned ((C . 2) (B . 3))
  1: SORT-BY-FREQ returned ((B . 3))
0: SORT-BY-FREQ returned ((B . 3))

可以看到的一个小问题是 last-element-by-pair被多次调用且输入相同, 是浪费。可以在循环之前调用一次。

在级别0处,该函数遍历对,直到找到(B . 3), 其频率等于第二个列表中的第一个频率。这是 步骤4,然后将配对推到由 调用listRet的本地变量sort-by-freq

当循环到达无序列表的最后一个元素时, 用(i)新的无序对列表递归调用函数, 使用remove-element进行计算,(ii)减少一个频率,以及(iii)当前 列表绑定到listRet

但是无论在递归步骤中发生什么,特别是无论递归调用产生什么结果,当前绑定 listRet的作用域范围将不再被修改。另外, listRet当前指向的列表的结构未修改 (例如,使用nconcrplacd)。

在2级及以下级别完成的所有工作都是关于将值推入 本地命名为listRet的临时变量的前面,然后将其丢弃。

  

我想念什么?

数据从sort-by-freq的递归调用流向 该函数的当前调用已损坏,您必须表达 当前结果(递归结果),或者您需要进行突变 事物(但是不鼓励这样做)。

命名和用户界面

  

获取无序对列表,已排序频率列表以及   可选的startList用于递归调用。首先设置listRet   等于startList。

根据您的规范,我将函数定义如下:

(defun sort-pairs-by-frequencies (pairs frequencies)
  ...)

是否需要其他参数进行递归 调用对于该函数的用户而言不是问题。那样 细节应保留为隐藏状态,除非您确实想要用户 才能将列表作为第三个参数传递。

复杂的角落案例

  

如果无序列表仅包含一个元素,则将该元素压入   列出Ret。 [...]如果不是最后一个元素,则将其推送到   listRet。

遍历列表的函数通常只需要考虑两个 情况:空列表和非空列表。您考虑更多的事实 极端情况,例如一个元素的列表或检查您是否 元素是最后一个,是一个巨大的危险信号。有问题 需要具有复杂的基本条件,但这可以在一个条件下完成 更简单的方法。

冗余测试

除了多次致电last-element-by-pair,还请注意 您在功能的不同点重复测试,即使 在给定的上下文中它们一定是正确的。

 (if (and (equal (cdr pair) (car listOrdFreqs))
          (not (equal (last-element-by-pair listUnord)(car pair))))
     (push pair listRet) ;;step 4
     (if (and (equal (cdr pair) (car listOrdFreqs))
              (equal (last-element-by-pair listUnord)(car pair)))
         (sort-by-freq (append (list (cons (car pair) (cdr pair)))
                               (remove-element listUnord pair))
                       listOrdFreqs listRet) ;;step 6
         (if (equal (last-element-by-pair listUnord)(car pair))
             (sort-by-freq (remove-element listUnord (car listRet))
                           (cdr listOrdFreqs)
                           listRet))))

给出适当的定义,可以将上面的内容写成:

(if (and A (not B))
   (step-4)
   (if (and A B)
        (step-6)
        (if B (step-5))))

让我们根据每个步骤写出每个步骤适用的条件 它们属于if表达式的树中的分支:

  • 对于步骤4:(and a (not b))必须成立,因为它位于if的“ then”分支中。

  • 对于步骤5:(and b (not a)),基于对路径谓词的以下简化:

    (and b
         (not (and a b))
         (not (and a (not b))))
    
    => (and b
            (or (not a) (not b))
            (or (not a) b))
    
    => (and b (or (not a) nil) t)
    => (and b (not a))
    
  • 对于步骤6:(and a b)

因此,您可以将测试编写为:

(cond
  (a (if b (step-6) (step-4)))
  (b (step-5)))

简单版本

  

如果列表大于1,则每对不规则列表为   遍历并检查它是否等于   有序频率列表。

以上是算法的核心。对于每个频率F 将无序元素划分为两个列表:其中一个 频率等于F,其他等于。

让我们定义一下:如何根据是否要删除或保留项目 它们的频率匹配给定的频率。以下不是 一定有效,但它既有效又简单。而且,它会出错 通过仅使用不可变操作来保持谨慎:

(defun remove-frequency (pairs frequency)
  (remove frequency pairs :test #'= :key #'cdr))

(defun keep-frequency (pairs frequency)
  (remove frequency pairs :test-not #'= :key #'cdr))

(remove-frequency '((a . 20) (b . 10) (c . 5) (d . 20)) 20)
=> ((B . 10) (C . 5))

(keep-frequency '((a . 20) (b . 10) (c . 5) (d . 20)) 20)
=> ((A . 20) (D . 20))

然后,您的主要功能在频率上递归:

(defun sort-pairs-by-frequencies (pairs frequencies)
  (if (null frequencies)
      pairs
      (destructuring-bind (frequency . frequencies) frequencies
        (append (keep-frequency pairs frequency)
                (sort-pairs-by-frequencies (remove-frequency pairs frequency)
                                           frequencies)))))

当没有给定频率进行排序时,未排序的对列表为 回到。否则,frequencies可以被分解为一个cons单元格, 其中第一项是frequency,其余项是 frequencies。请注意,frequencies的先前绑定是 阴影,因为从这一点上我们不需要参考 整个频率列表了。

然后,一般情况下函数的返回值由下式计算 附加具有给定频率的对和列表 对与frequency不匹配的对,根据 其余频率。

(sort-pairs-by-frequencies '((a . 20) (b . 10) (c . 5) (d . 20))
                           '(5 10 20))
=> ((C . 5) (B . 10) (A . 20) (D . 20))

通过首先评估(trace sort-pairs-by-frequencies),您还 获取以下跟踪:

 0: (SORT-PAIRS-BY-FREQUENCIES ((A . 20) (B . 10) (C . 5) (D . 20)) (5 10 20))
   1: (SORT-PAIRS-BY-FREQUENCIES ((A . 20) (B . 10) (D . 20)) (10 20))
     2: (SORT-PAIRS-BY-FREQUENCIES ((A . 20) (D . 20)) (20))
       3: (SORT-PAIRS-BY-FREQUENCIES NIL NIL)
       3: SORT-PAIRS-BY-FREQUENCIES returned NIL
     2: SORT-PAIRS-BY-FREQUENCIES returned ((A . 20) (D . 20))
   1: SORT-PAIRS-BY-FREQUENCIES returned ((B . 10) (A . 20) (D . 20))
 0: SORT-PAIRS-BY-FREQUENCIES returned ((C . 5) (B . 10) (A . 20) (D . 20))

编写上述函数是为了使它们易于阅读 和“平凡”正确,但它们仍在浪费内存和堆栈。其他方法也可以使用循环(恒定的堆栈使用量)对元素进行排序(不分配内存)。

答案 2 :(得分:0)

我用一个不太复杂的解决方案解决了这个问题。我承认这里的其他张贴者写出了更为优雅的解决方案,但它们与我的尝试无关。我想使用目前已经了解的基本语法来解决这个问题。

这是我的新sort-by-frequency函数的样子:

(defun sort-by-frequency (pairs frequencies)
  (let ((retList (list)))
        (loop for freq in frequencies
           do (push (extract-pair-match pairs freq (extract-keys retList)) retList))
    retList))

我现在使用一个简单的循环遍历频率列表,然后使用函数extract-pair-match根据频率找到匹配项,该函数还从变量{中获取键(extract-keys) {1}},以便它可以搜索并确保同一键不会出现两次。

功能如下:

retList

首先,它检查列表(defun extract-pair-match(pairs match keys) (if (and (equal (cdr(car pairs)) match) (not (search-keys keys (car(car pairs))))) (car pairs) (extract-pair-match (cdr pairs) match keys))) 的第一项,以查看其键是否与pairs相匹配,然后使用match列表上的函数search-keys(在keys的{​​{1}}处传递以确保该键尚未包含在列表中,如果它继续到下一个术语,则假定每个频率将有一个匹配项。

这是retList函数:

sort-by-frequency

这是search-keys函数:

(defun search-keys (keys match)
  (if (null keys)
      nil
      (if (equal (car keys) match)
      (car keys)
      (search-keys (cdr keys) match))))

现在,如果我这样做:

extract-keys

我得到:

(defun extract-keys (pairList)
  (let ((retlist (list (car (car pairList)))))
       (loop for pair in (cdr pairList)
          do (push (car pair) retlist))
    retlist))