【问题标题】:Creating a custom reverse of list创建列表的自定义反向
【发布时间】:2017-11-06 03:10:38
【问题描述】:

我正在尝试在 Lisp 中创建一个自定义的反向列表。我对 Lisp 编程很陌生,仍然在语法上苦苦挣扎。这是我目前的代码

(defun new-union(l1 l2)
    (setq l (union l1 l2))
       (let (res) 
         (loop for x in l
           do(setq res (cons (car l) res))
           do(setq l (cdr l)))))

在这里,我将列出两个列表,并形成联合列表 l。然后为了反转列表l,我正在明智地访问元素以将其附加到新列表res。然后使用conscarcdr 更新列表。 但是,我得到了一个奇怪的输出。有人可以建议我哪里出错了吗?

我知道一个名为 nreverse 的内置函数,但我想尝试看看 Lisp 如何解释列表中的数据。

在最后打印res,例如

(new-union '(a b c) '(d e f))

上面调用的输出给了我

(L A A A A A A A X X)

我认为我做错了循环。

【问题讨论】:

  • 你是在执行反向还是联合?
  • 你永远不会返回资源。结果是最后一个表达式的值,这里是循环,它通过累加子句或 finally 子句不产生任何值。循环...做成语是 eq。顺便说一句,这里是一个dolist。
  • 另外,你在 L 上调用 setq,这很糟糕:L 应该是一个局部变量,用 let 声明,像 res。
  • 您在迭代 L 时对其进行修改:充其量您跳过列表中的所有其他元素,但这很危险:lispworks.com/documentation/lw51/CLHS/Issues/iss240_w.htm
  • 循环已经在 L 上进行了迭代,您只需通过将每个连续的 x 推到它前面来修改 res。你快到了。

标签: lisp common-lisp clisp


【解决方案1】:

问题

(以前的 cmets 总结)

  1. 错误的缩进、空格和名称;喜欢这个:

    (defun new-union (l1 l2)
      (setq list (union l1 l2))
      (let (reversed) 
        (loop for x in list
              do (setq res (cons (car list) reversed))
              do (setq list (cdr list)))))
    
  2. 在未声明的全局变量上使用 SETQ,而不是 LET

  3. 正在迭代的结构的突变 (LIST)
  4. 不在 LOOP 中使用 X(为什么要定义它?)
  5. 返回值始终为 NIL

重构

(defun new-union (l1 l2)
  (let ((reverse))
    (dolist (elt (union l1 l2) reverse)
      (push elt reverse))))
  • 定义一个本地的reverse变量,默认绑定到NIL(你可以将它设置为'(),这有时是首选)。
  • 使用DOLIST 遍历列表并执行副作用;第三个参数是返回值;在这里,您可以将 reverse 变量放在我们累积反向列表的位置。
  • 对于每个元素elt,将其推到reverse前面;如果您不想将push 用于学习目的,请使用(setf reverse (cons elt reverse))

Common Lisp 是多范式的,喜欢务实的解决方案:有时循环更自然或更高效,没有理由强迫自己采用函数式风格。

功能实现

但是,列表提供了一种自然的归纳结构:在某些情况下,递归方法可能更合适。 如果您想使用函数式风格来计算反向,请注意尾调用优化虽然很常见,但语言规范并不要求(这取决于您的实现能力和编译器选项)。

使用默认设置,SBCL 消除了尾部位置的调用,并消除了大输入时堆栈溢出的风险。但是如果你不小心的话,还有其他可能的方法来获得糟糕的算法复杂性(和浪费的代码)。 以下是我用来定义联合和反向组合的内容;特别是,我更喜欢使用 labels 定义一个本地函数,以避免使用虚拟 nil 参数调用 new-union。另外,我只对联合生成的列表进行了一次迭代。

(defun new-union (l1 l2)
  (labels ((rev (list acc)
             (etypecase list
               (null acc)
               (cons (rev (rest list)
                          (cons (first list) acc))))))
    (rev (union l1 l2) nil)))

跟踪

  0: (NEW-UNION (A B C) (D E F))
    1: (UNION (A B C) (D E F))
    1: UNION returned (C B A D E F)
    1: (REV (C B A D E F) NIL)
      2: (REV (B A D E F) (C))
        3: (REV (A D E F) (B C))
          4: (REV (D E F) (A B C))
            5: (REV (E F) (D A B C))
              6: (REV (F) (E D A B C))
                7: (REV NIL (F E D A B C))
                7: REV returned (F E D A B C)
              6: REV returned (F E D A B C)
            5: REV returned (F E D A B C)
          4: REV returned (F E D A B C)
        3: REV returned (F E D A B C)
      2: REV returned (F E D A B C)
    1: REV returned (F E D A B C)
  0: NEW-UNION returned (F E D A B C)

备注

当联合应该对无序集进行操作时,反转union 的结果是相当令人惊讶的:结果中元素的顺序不必反映 list-1 或 list 的顺序-2 无论如何。 集合是没有重复的无序集合;如果您的输入列表已经代表集合,正如函数名称 (new-union) 所暗示的那样,那么删除重复项或期望顺序有意义是没有意义的。

如果输入列表表示值序列,那么顺序很重要;随意将appendconcatenateremove-duplicates 结合使用,但请注意后者默认会删除列表前面的元素:

(remove-duplicates (concatenate 'list '(4 5 6) '(2 3 4)))
=> (5 6 2 3 4)

您可能想改用:from-end t

【讨论】:

  • 看起来你的反转功能实际上并没有反转......它会随机播放
【解决方案2】:

好的...我认为您想获取两个列表,将它们组合在一起,删除重复项,然后将它们反转。

您最大的问题是您使用的是循环而不是递归。 LISP 的诞生就是为了使用递归进行列表处理。它更自然。

下面是一个非常简单的例子:

(defvar l1 '(a b c))      ;first list
(defvar l2 '(d e f))      ;second list

(defun my-reverse (a b)   ;a and b are lists
  "combines a and b into lst, removes duplicates, and reverses using recursion"
  (let ((lst (remove-duplicates (append a b))))   
    (if (> (length lst) 0)
        (append (last lst) (my-reverse nil (butlast lst)))
        nil)))

使用 SBCL 在 SLIME 中编译的示例运行

; compilation finished in 0:00:00.010                                                                                                                                    
CL-USER> l1 ;; verify l1 variable
(A B C)                                                                                                                                                                  
CL-USER> l2 ;; verify l2 variable
(D E F)                                                                                                                                                                  
CL-USER> (append l1 l2) ;; append l1 and l2
(A B C D E F)                                                                                                                                                            
CL-USER> (my-reverse l1 l2) ;; reverse l1 and l2
(F E D C B A)   

【讨论】:

  • 我没有看到问题中删除重复项的要求。
  • OP 在他的代码示例和解释中使用了 union。 Union 删除了重复项,因此我在解决方案中删除了重复项。
  • 规范不要求:如果 list-1 或 list-2 中有重复条目,则冗余条目可能会出现在结果中,也可能不会出现。 (@ 987654321@)
  • OP 没有询问 Common Lisp 的缺陷。并集来自集合论,集合不包含重复项。集合论的使用向我表明 OP 想要删除重复项。
  • 是的。我认为工会隐含地处理重复项。所以就这样去做了。另外,感谢您提供有关递归函数的提示。我更习惯于来自 oop​​s 编程的循环。
猜你喜欢
  • 1970-01-01
  • 2016-04-30
  • 2014-12-10
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 2022-01-16
相关资源
最近更新 更多