如何导出函数segs?

时间:2012-01-09 22:44:46

标签: 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中描述的设计原则来解决这个问题(不,这不是书中的问题,所以请随意讨论它!)如何解决?在程序推导中使用哪些原则?

2 个答案:

答案 0 :(得分:6)

首先建立一组相关的例子,最重要的是:

(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)))

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

所以保持main函数并写入non-empty-prefixes

non-empty-prefixes : list -> (listof non-empty-list)

使用该辅助函数,可以轻松编写主函数。

(可选)由此产生的朴素函数的复杂性很差,因为它会重复调用non-empty-prefixes。考虑(segs (cons head tail))。它会调用(non-empty-prefixes tail)两次:一次是因为它调用(segs tail)调用(non-empty-prefixes tail),然后再调用(non-empty-prefixes (cons head tail))来调用(non-empty-prefixes tail)递归调用(segs tail)。这意味着天真的功能具有不必要的复杂性。

问题是(non-empty-prefixes tail)计算(segs (cons head tail))然后忘记它,因此segs必须重做工作。解决方案是通过将non-empty-prefixessegs+ne-prefixes : list -> (values (listof list) (listof non-empty-list)) 融合到一个计算两个答案的函数中来保留这些额外信息:

segs

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

(已编辑添加)关于non-empty-prefixes:这是定义;; non-empty-prefixes : list -> (listof non-empty-list) (define (non-empty-prefixes lst) (cond [(empty? lst) empty] [(cons? lst) (map (lambda (p) (cons (first lst) p)) (cons '() (non-empty-prefixes (rest lst))))])) 的一种方法。 (注意:空列表没有非空前缀。不需要引发错误。)

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)))]))

你可以像这样融合他们:

values

这个函数仍然遵循设计配方(或者,如果我已经展示了我的测试,它会:)特别是,它使用模板在列表上进行结构递归。 HtDP没有谈论let-valuesnon-empty-prefixes,但您可以使用辅助结构对信息进行分组。

HtDP稍微讨论了复杂性,但这种计算重新组合通常在算法课程中更多地讨论,在“动态编程和记忆化”下。请注意,融合这两个函数的替代方法是memoize append;这也将修复复杂性。

最后一件事:在(append ne-prefixes segs-of-rest)附近的论点应该颠倒到{{1}}。 (当然,这意味着重写所有测试以使用新订单,或者编写/查找对顺序不敏感的列表比较函数。)尝试在大型列表(大约300-400个元素)上对函数的两个版本进行基准测试,看看你是否可以分辨出来,看看你是否可以解释它。 (这是更多的算法材料,而不是设计。)

答案 1 :(得分:0)

正在进行2次递归:左边的第一个印章原子,右边的第二个印章原子。以下是我用2个函数递归解决的问题,简单来说(因为我不熟悉Scheme):

Start with the FullList variable in function A, 
accumulate right-chop results on FullList from recursive function B, 
then chop off the left character of FullList and call function A with it. 
Stop when an empty list is received. 

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