【问题标题】:Subset / Subsequence Recursive Procedure in Simply Scheme LispSimply Scheme Lisp 中的子集/子序列递归过程
【发布时间】:2019-11-30 23:36:54
【问题描述】:

我正在结合来自伯克利的 2011 年夏季 CS3 课程学习简单计划。我正在努力理解subset / subsequence 程序。收到解决方案代码后,我了解了基本机制,但我很难掌握足够的概念以自己提出解决方案。

谁能给我指出一些可以帮助我更好地理解它的方向?还是自己解释不同?

这是我目前所了解的基础:

因此,在以下过程中,作为prepend 的参数的subsequences 递归调用将word 分解为其基本元素,而prepend 将添加@ 的first 987654329@ 到每个元素。

; using words and sentences

(define (subsequences wd)
  (if (empty? wd)
      (se "")
      (se (subsequences (bf wd))
          (prepend (first wd) 
                   (subsequences (bf wd))))))

(define (prepend l wd)
  (every (lambda (w) (word l w))
         wd))

; using lists

(define (subsequences ls)
  (if (null? ls)
      (list '())
      (let ((next (subsequences (cdr ls))))
        (append (map (lambda (x) (cons (car ls) x))
                     next)
                next)))) 

所以第一个,当输入(subsequences 'word) 时,会返回:

    ("" d r rd o od or ord w wd wr wrd wo wod wor word)

第二个,当输入(subsequences '(1 2 3))时,会返回:

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

所以,正如我所说,这段代码有效。我单独理解代码的每个部分,并且在大多数情况下,它们是如何相互协作的。嵌套递归调用给我带来了麻烦。我只是不完全理解它,无法自己编写这样的代码。任何能够帮助我理解它的东西都将不胜感激。我想我只需要一个新的视角来思考它。

提前感谢任何愿意为我指明正确方向的人。

编辑:

所以第一条评论要求我尝试多解释一下我目前所了解的内容。就是这样:

对于单词/句子过程,我认为它通过第二个出现的递归调用将变量分解为“最基础”的情况(可以这么说)。

然后它基本上是在基本情况的基础上构建的。

我真的不明白为什么首先出现的递归调用需要在那里。

在列表之一中,当我自己编写时,我得到了这个:

(define (subseq lst)
  (if (null? lst)
      '()
      (append (subseq (cdr lst))
              (prepend (car lst)
                       (subseq (cdr lst))))))

(define (prepend i lst)
  (map (lambda (itm) (cons i itm)) 
       lst))

使用正确的解决方案,在我看来,列表中的car 会掉线而不会被计算在内,但显然情况并非如此。我不明白这两个递归调用是如何协同工作的。

【问题讨论】:

  • “我理解代码的每个部分,并且在大多数情况下,它们是如何相互配合的。”我建议用你自己的话描述你的内容了解到此为止。这不仅可以帮助您自己整理想法,而且对任何想回答您的问题的人都有很大帮助。 [编辑:将块引用降价调整为引号 -- Baum mit Augen]
  • bfse 不是标准的 Scheme 函数;你能在问题中包含他们的定义吗?
  • 我会避免两次调用(subseq (cdr lst));您可以使用let 在变量中捕获它。 (let ((scl (subseq cdr lst))) (append scl (prepend (car lst) scl))).
  • @Kaz 原来“Simply Scheme Lisp”是googlable。 :)

标签: list recursion scheme lisp sequence


【解决方案1】:

您的替代解决方案大部分都很好,但是您在第一次实现此(列表的幂集)功能时犯了许多人犯的相同错误:您的基本情况是错误的。

有多少种方法可以从 0 元素列表中选择 0 个或多个项目的子集? “0”可能感觉很明显,但实际上有一种方法:不选择任何项目。因此,您应该返回(list '())(意思是“一种方法的列表,即不选择任何元素”),而不是返回空列表(意思是“没有办法做到”)。等效地,您可以返回 '(()),这与 (list '()) 相同 - 我不知道好的 Scheme 样式,所以我将其留给您。

一旦您进行了更改,您的解决方案就可以工作,这表明您实际上确实理解了递归!


至于解释提供给您的解决方案,我不太明白您认为列表中的car 会发生什么。它实际上与您自己编写的算法几乎完全相同:要查看它有多接近,请内联您对 prepend 的定义(即,将其主体替换为您的 subsequences 函数)。然后从提供的解决方案中扩展let 绑定,将其主体替换为它出现的两个位置。最后,如果需要,可以将参数的顺序交换为 append - 或不;没关系。此时,它与您编写的函数相同。

【讨论】:

  • '(()) 很有意义。谢谢!
【解决方案2】:

Recursion 是一个帮助我们的工具,让编程更容易

递归方法不会尝试一次解决整个问题。它说,如果我们已经有了解决方案代码怎么办?然后我们可以将它应用到原始问题的任何类似的smaller部分,并得到it的解决方案。然后我们所要做的就是将剩余的 "shell"result 重新组合,其中包含 smaller 自相似部分对于那个较小的部分;这样我们就可以得到完整问题的完整解决方案!

因此,如果我们能够识别我们的数据中的递归结构;如果我们可以把它拆开,就像Russian "matryoshka" doll 在它的 shell 内包含它自己的较小的副本(它也包含较小的 self 副本内部,一直向下)并且可以放回;那么我们要改造整个“娃娃”所需要做的就是改造其中包含的嵌套“俄罗斯套娃”娃娃(所有嵌套的娃娃都在里面——我们不在乎有多少层深!)通过应用我们正在寻求创建的递归过程,并简单地放回 结果

    solution( shell <+> core )   ==   shell  {+}  solution( core )
;;            --------------                                ----

等式两边的两个+s是不同的,因为变形后的娃娃可能根本就不是娃娃! (另外,左侧的&lt;+&gt;解构给定的数据,而右侧的{+}构建整体结果。)

这是您的函数中使用的recursion scheme

有些问题更适合其他递归方案,例如分而治之可以更好地完成各种类型、Voronoi 图等:

    solution( part1 <+> part2 )   ==   solution( part1 ) {+} solution( part2 )
;;            ---------------                    -----                 -----

至于两个——或一个——递归调用,因为这是一个数学上的函数,所以用相同的参数调用它的结果总是相同的。没有语义差异,只有操作差异。

有时我们更喜欢计算一个结果并将其保存在内存中以供进一步重用;有时我们更喜欢在每次需要时重新计算它。就最终结果而言,这无关紧要 - 将计算相同的结果,唯一的区别是消耗的内存和/或产生该结果所需的时间。

【讨论】:

  • 非常感谢您花时间以不同的方式向我解释这一点。在你和 amalloy 之间,它开始变得更加清晰。再次感谢!
  • 不客气。 :) 看看this 你是否也很感兴趣... :)
猜你喜欢
  • 2010-12-12
  • 1970-01-01
  • 2021-09-20
  • 2015-12-12
  • 2023-04-01
  • 2013-10-25
  • 1970-01-01
  • 2018-12-29
  • 1970-01-01
相关资源
最近更新 更多