【问题标题】:Build list in Lisp using an iterative recursive function使用迭代递归函数在 Lisp 中构建列表
【发布时间】:2019-02-27 18:35:12
【问题描述】:

Lisp 的超级新手,但至少我正在尝试,如果这种方法看起来有点奇怪,请原谅我。
我乐于学习新想法,但我肯定需要了解我的方法有什么问题。
我有一个使用迭代递归方法构建列表的函数。

(defun create-tree-iteratively(sub-list)
    (if (equal (length sub-list) 1)
        sub-list
        (loop for i in sub-list
            do(setq subtree (list i))
            do(setq sub-sub-list (remove i sub-list))
            do(append subtree (create-tree-iteratively sub-sub-list))
        )
    )
)

我的程序的输入是

'(1 2 3)

预期输出是

'((1 (2 3) (3 2)) (2 (1 3) (3 1)) (3 (1 2) (2 1)))

我的循环(递归)运行文件。我在适当组合递归输出时遇到问题。

【问题讨论】:

  • 调用长度不是一个好主意,因为它会遍历整个列表。 APPEND 返回一个列表。你没有使用它。这也不是一个好主意。 sub-treesub-sub-list 是未定义的变量。也不好。
  • 你能描述一下你试图实现的算法的目的是什么(不仅仅是一个案例)。

标签: list lisp common-lisp


【解决方案1】:

代码风格

  • 最好不要在自己的行上有右括号,通常 Lisp 中的约定是在末尾分组括号,如下所示: ))).

  • 由于 cons-cells 只有两个插槽,CARCDR,当它们被用作列表时,它们不包含 列表的长度。所以唯一的办法 计算长度就是遍历整个细胞链,也就是 您的功能已经在做什么。如果您的列表大小为 N,则必须 计算长度 N 次,这使得函数中的步数与 N*N 成正比。

  • 在一个循环中,一个DO后面可以跟多个表达式,不需要 重复 DO 关键字。另外,在左括号前添加空格。

  • SETQ 不应该与未绑定的变量一起应用,例如 subtreesub-sub-list。你首先应该有一个周围的let 你介绍 局部变量(例如围绕loop),或在您的 环形。或者更好的是,使用 LOOP 的现有设施来避免进行突变 自己。

  • APPEND 的返回值很重要,因为它是 附加参数的结果(未修改)。但在这里你做 不使用返回值,这使得整个表达式无用。

另类

不用计算长度,只需检查输入是否 列表为空,包含一个或多个元素(不计算)。此外,您还可以 使用collect 将所有树收集为一个列表。我不确定结果 单例输入列表是正确的,应该是(list list)

(defun create-tree (list)
  (if (null (rest list))
      ;; covers both empty list and list with a single element
      list
      ;; otherwise, collect a list of trees
      (loop
        for i in list
        ;; collect all trees rooted at i, where a tree is a list (r c1 .. cn)
        ;; with R the root node and C1...CN each child tree. The child trees
        ;; are build recursively, with i removed from the list of values.
        collect (list* i (create-tree (remove i list))))))

【讨论】:

  • 在谷歌搜索后做了类似的事情。你能告诉我你为什么用 list* 而不是 list。
  • @trickster 例如,(list* 1 2 '(3 4 5)) 生成列表(1 2 3 4 5);最后一个元素是一个列表;作为比较,使用list,所有参数都应该是元素,所以结果将是(1 2 (3 4 5))。在我们只在列表前面添加一个元素的特殊情况下,可以将其替换为(cons i ...)。我这样做是为了匹配预期的输出。
【解决方案2】:

一些初始注释。

  • 当问一个涉及实现算法的问题时描述算法:根据一个例子来猜测你想要什么并不容易(下面我做了两个猜测)。
  • 我猜你以前用 Python 编写过,因为你的代码显示出明显的“Python 脑损伤”迹象(请注意,这是关于 Python 的评论,我已经为此花费了数年时间,而不是关于你的能力)。尤其:
    • Lisp 不会像 Python 那样将创建新绑定(变量)的表单与赋值混淆。您不会通过setq 在函数中创建新绑定,而是通过let 等某种绑定形式创建它;
    • 您不会通过append 在列表末尾添加新内容,而是创建一个新列表,其中添加了新内容,并且由于列表是链表而不是拖动中的可变长度数组, append 花费的时间与列表的长度成正比。
  • 但在某个方面,您的代码忽略了 Python 的一个重要教训:所有这些组结束标记对于阅读代码的任何人都无关紧要,并且您不应使用单个组结束(或开始)标记填充行。它们只是使阅读代码变得困难的噪音。事实上,在 Python 中,它们是如此不可见,以至于根本不存在。这是 Python 做对的事情之一。

话虽如此,以下是我认为您想要的三个版本:第一个实现了我认为的一致算法,第二个实现了我认为您可能想要的,以及最后一个抽象出终止测试并可以做任何一个(或其他任何事情)。

(defun make-permuted-tree (l)
  ;; this builds the tree all the way down
  (if (null l)
      '()
    (loop for e in l
          collect (cons e (make-permuted-tree (remove e l))))))

(defun make-permuted-tree/strange (l)
  ;; this stops before the end
  (if (null (rest l))
      l
    (loop for e in l
          collect (cons e (make-permuted-tree/strange (remove e l))))))

(defun make-permuted-tree/general (l &key (base-test (lambda (b)
                                                       (null b))))
  ;; this stops where you want it to, which by default is at the end
  (labels ((make-permuted-tree (lt)
             (if (funcall base-test lt)
                 lt
               (loop for e in lt
                     collect (cons e (make-permuted-tree (remove e lt)))))))
    (make-permuted-tree l)))

例如:

> (make-permuted-tree/strange '(1 2 3))
((1 (2 3) (3 2)) (2 (1 3) (3 1)) (3 (1 2) (2 1)))

> (make-permuted-tree '(1 2 3))
((1 (2 (3)) (3 (2))) (2 (1 (3)) (3 (1))) (3 (1 (2)) (2 (1))))

> (make-permuted-tree/general '(1 2 3))
((1 (2 (3)) (3 (2))) (2 (1 (3)) (3 (1))) (3 (1 (2)) (2 (1))))

> (make-permuted-tree/general '(1 2 3) :base-test (lambda (b)
                                                    (null (rest b))))
((1 (2 3) (3 2)) (2 (1 3) (3 1)) (3 (1 2) (2 1)))

【讨论】:

  • 谢谢。我对 Lisp 的了解非常有限。我只知道一些宏(甚至不确定 lisp 宏到底是什么),就像你猜想的那样,我以前用 python 编码过(但我主要用 JS 编码),而且我喜欢很多缩进。再次感谢您不厌其烦地详细解释。我没有提到整个算法,因为我的问题已经相当进化了,我正在尝试分块进行。
  • @trickster 分块做事是一个很好的方法,但是,请解释你的分块应该做什么。
猜你喜欢
  • 2018-06-19
  • 2014-12-23
  • 2016-01-22
  • 1970-01-01
  • 1970-01-01
  • 2019-04-03
  • 2022-01-15
  • 1970-01-01
  • 2012-06-15
相关资源
最近更新 更多