Recursion 如何推导函数分段?

Recursion 如何推导函数分段?,recursion,functional-programming,scheme,racket,recurrence,Recursion,Functional Programming,Scheme,Racket,Recurrence,如何构造函数segs,返回列表中所有连续段的列表? 例如,(segs'(l i s t))应产生以下答案: (() (t) (s) (s t) (i) (i s) (i s t) (l) (l i) (l i s) (l i s t)) 我特别感兴趣的是如何根据HtDP中描述的设计原则解决这个问题(不,这不是书中的问题,所以请随意讨论!)如何解决?在程序派生中使用哪些原则 有两个递归:第一个从左边切掉原子,第二个从右边切掉原子。下面是我如何在两个函数中递归地解决它,简单地说(因为我对Schem

如何构造函数
segs
,返回列表中所有连续段的列表? 例如,
(segs'(l i s t))
应产生以下答案:

(() (t) (s) (s t) (i) (i s) (i s t) (l) (l i) (l i s) (l i s t))

我特别感兴趣的是如何根据HtDP中描述的设计原则解决这个问题(不,这不是书中的问题,所以请随意讨论!)如何解决?在程序派生中使用哪些原则

有两个递归:第一个从左边切掉原子,第二个从右边切掉原子。下面是我如何在两个函数中递归地解决它,简单地说(因为我对Scheme不熟练):


积累所有结果,你就是黄金。

从建立一组相关示例开始,最简单的是:

(equal? (segs '())
        (list '()))
(equal? (segs '(z))
        (list '()
              '(z)))
(equal? (segs '(y z))
        (list '() '(z)
              '(y) '(y z)))
(equal? (segs '(x y z))
        (list '() '(z) '(y) '(y z)
              '(x) '(x y) '(x y z)))
通过查看示例,您可以观察到(我使用了格式来突出显示):每个示例的答案都包括上一个示例答案中的所有元素。事实上,非空列表的连续子序列只是其尾部的连续子序列以及列表本身的非空前缀

因此,搁置主函数并写入
非空前缀

non-empty-prefixes : list -> (listof non-empty-list)
有了这个helper函数,编写主函数就很容易了

(可选)生成的naive函数的复杂性很差,因为它重复调用
非空前缀
。考虑<代码>(SEGS(CONS头尾))< /代码>。它调用
(非空前缀尾)
两次:一次是因为它调用
(segs尾)
,后者调用
(非空前缀尾)
,再次是因为它调用
(非空前缀尾)
,后者递归调用
(非空前缀尾)
。这意味着朴素函数具有不必要的糟糕复杂性

问题是
(segs tail)
计算
(非空前缀tail)
,然后忘记它,因此
(segs(cons head tail))
必须重做这项工作。解决方案是通过将
segs
非空前缀
融合到一个计算两个答案的函数中来保留这些额外信息:

segs+ne-prefixes : list -> (values (listof list) (listof non-empty-list))
然后将
segs
定义为只删除第二部分的适配器函数。这解决了复杂性的主要问题

(编辑添加)关于
segs+ne前缀
:以下是定义
非空前缀
的一种方法。(注意:空列表没有非空前缀。无需引发错误。)

segs
看起来是这样的:

;; segs : list -> (listof list)
(define (segs lst)
  (cond [(empty? lst) (list '())]
        [(cons? lst)
         (append (segs (rest lst))
                 (non-empty-prefixes lst))]))
;; segs+ne-prefixes : list -> (values (listof list) (listof non-empty-list))
;; Return both the contiguous subsequences and the non-empty prefixes of lst
(define (segs+ne-prefixes lst)
   (cond [(empty? lst)
          ;; Just give the base cases of each function, together
          (values (list '())
                  empty)]
         [(cons? lst)
          (let-values ([(segs-of-rest ne-prefixes-of-rest)
                        ;; Do the recursion on combined function once!
                        (segs+ne-prefixes (rest lst))])
            (let ([ne-prefixes
                   ;; Here's the body of the non-empty-prefixes function
                   ;; (the cons? case)
                   (map (lambda (p) (cons (first lst) p))
                        (cons '() ne-prefixes-of-rest))])
              (values (append segs-of-rest ne-prefixes)
                      ne-prefixes)))]))
你可以像这样融合它们:

;; segs : list -> (listof list)
(define (segs lst)
  (cond [(empty? lst) (list '())]
        [(cons? lst)
         (append (segs (rest lst))
                 (non-empty-prefixes lst))]))
;; segs+ne-prefixes : list -> (values (listof list) (listof non-empty-list))
;; Return both the contiguous subsequences and the non-empty prefixes of lst
(define (segs+ne-prefixes lst)
   (cond [(empty? lst)
          ;; Just give the base cases of each function, together
          (values (list '())
                  empty)]
         [(cons? lst)
          (let-values ([(segs-of-rest ne-prefixes-of-rest)
                        ;; Do the recursion on combined function once!
                        (segs+ne-prefixes (rest lst))])
            (let ([ne-prefixes
                   ;; Here's the body of the non-empty-prefixes function
                   ;; (the cons? case)
                   (map (lambda (p) (cons (first lst) p))
                        (cons '() ne-prefixes-of-rest))])
              (values (append segs-of-rest ne-prefixes)
                      ne-prefixes)))]))
这个函数仍然遵循设计方法(或者,如果我展示了我的测试,它会这样做):特别是,它使用模板进行列表上的结构递归。HtDP没有讨论
让值
,但是可以使用辅助结构对信息进行分组

HtDP稍微谈到了复杂性,但这种计算的重新组合通常在“动态规划和记忆”的算法课程中讨论得更多。请注意,融合这两个函数的另一种方法是记忆
非空前缀
;这也将修复复杂性


最后一件事:靠近末尾的
append
的参数应该反转为
(append ne prefixes segs of rest)
。(当然,这意味着重写所有测试以使用新的顺序,或者编写/查找不区分顺序的列表比较函数。)尝试在一个大的列表(大约300-400个元素)上对该函数的两个版本进行基准测试,看看是否可以分辨出差异,并看看是否可以解释。(这更多的是算法材料,而不是设计。)

若要澄清问题,请对答案发表评论和/或修改您的问题,不要尝试编辑答案本身。Ryan,您能解释一下我需要应用哪些机制才能获得此“融合”segs+ne前缀功能吗?@Racket Noob,我已在回答的末尾添加了一个解释。