将方案中具有两次递归调用的函数转换为尾递归

发布于 2024-10-24 04:20:17 字数 499 浏览 1 评论 0 原文

在开始之前:是的,这是大学的作业。在我被告知我又懒又邪恶之前:这部分作业是转换我们已有的两个函数,这是第六个。

(define (flatten-list a-list)
  (cond ((null? a-list) '())
      ((list? (car a-list)) 
       (append (flatten-list (car a-list)) (flatten-list (cdr a-list))))
      (else (cons (car a-list) (flatten-list (cdr a-list))))))

正如您可以猜到的那样,即使列表是嵌套的,该函数也会展平列表。我的具体转换问题出现在 (list? (car a-list)) 条件中,其中我正在进行两次递归调用。我已经做了斐波那契数列,只需在尾递归上使用两个“累加器”就可以做到这一点。然而,我的头脑还没有接受过这方面的训练,还不知道应该如何进行。

如果我得到提示而不是结果,我将不胜感激。谢谢!

Before I start: YES, this is a homework from college. Before I get told that I'm lazy and evil: this part of the homework was to convert two functions we already had, this one is the 6th.

(define (flatten-list a-list)
  (cond ((null? a-list) '())
      ((list? (car a-list)) 
       (append (flatten-list (car a-list)) (flatten-list (cdr a-list))))
      (else (cons (car a-list) (flatten-list (cdr a-list))))))

The function, as you can guess, flattens a list even if it's nested. My specific problem with the transformation comes in the (list? (car a-list)) condition, in which I'm doing two recursive calls. I already did fibonacci, which I can do by just having two "acummulators" on the tail recursion. However, my mind is not trained in this yet to know how it should go.

I would appreciate if I was given hints and not the result. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

疧_╮線 2024-10-31 04:20:17

这是我的解决方案:

(define (flatten-iter a-list)
  (define (flat-do acc lst-interm lst)
    (cond 
      ((null? lst)
       (reverse acc))
      ((and (list? lst-interm) (not (null? lst-interm)))
       (flat-do acc (car lst-interm) (append (cdr lst-interm) lst)))
      ((not (list? lst-interm))
       (flat-do (cons lst-interm acc) empty lst))
      ((list? (car lst))
       (flat-do acc (car lst) (cdr lst)))
      (else
       (flat-do (cons (car lst) acc) empty (cdr lst)))))
  (flat-do empty empty a-list))

(flatten-iter (list 1 (list 2 (list 3 4 (list 5 empty 6))) 7 8))
=> (1 2 3 4 5 6 7 8)

尾递归函数要求它们永远不会返回,因此您不能使用堆栈来存储程序的状态。相反,您可以使用函数参数在函数调用之间传递状态。因此,我们需要确定如何维护状态。因为我们函数的结果是list?,所以增加一个列表是有意义的;为此,我们使用 acc。您可以在上面的 else 分支中看到它是如何工作的。但我们应该能够处理嵌套列表。当我们深入时,我们应该保留嵌套列表的其余元素以进行进一步处理。示例列表: (list 1 (list 2 3) 4 5)

直到 (list 2 3) 我们已经将 1 添加到累加器中。由于我们不能使用堆栈,因此我们需要其他地方来存储列表的其余元素。这个地方就是 lst 参数,它包含要展平的原始列表的元素。我们可以将lst附加到其余元素(cdr (list 2 3)),即(list 3) code>,然后继续我们在展平时偶然发现的列表头部,即 (car (list 2 3)),它只是 2。现在, (and (list? lst-interm) (not (null? lst-interm))) 成功,因为 flat-do 是这样调用的:

(flat-do (list 1) (list 2 3) (list 4 5))

并且条件触发此代码:

(flat-do (list 1) (car (list 2 3)) (append (cdr (list 2 3)) (list 4 5)))

flat-do 再次以这种方式调用: (flat-do (list 1) 2 (list 3 4 5))

条件 (not (list? 2)) 现在成功,并且代码 (flat-do (cons 2 1) empty (list 3 4 5)) 被评估。

其余处理通过 else 分支完成,直到 lstnull? 并在 acc 上执行 reverse 。然后函数返回反转后的累加器。

Here's my solution:

(define (flatten-iter a-list)
  (define (flat-do acc lst-interm lst)
    (cond 
      ((null? lst)
       (reverse acc))
      ((and (list? lst-interm) (not (null? lst-interm)))
       (flat-do acc (car lst-interm) (append (cdr lst-interm) lst)))
      ((not (list? lst-interm))
       (flat-do (cons lst-interm acc) empty lst))
      ((list? (car lst))
       (flat-do acc (car lst) (cdr lst)))
      (else
       (flat-do (cons (car lst) acc) empty (cdr lst)))))
  (flat-do empty empty a-list))

(flatten-iter (list 1 (list 2 (list 3 4 (list 5 empty 6))) 7 8))
=> (1 2 3 4 5 6 7 8)

Tail-recrusive functions require that they never return, and thus you can't use stack for storing your program's state. Instead, you use function arguments to pass the state between function calls. Therefore, we need to determine how to maintain the state. Because the result of our function is list?, it's meaningful to grow an empty list; we're using acc for this purpose. You can see how it works in else branch above. But we should be able to process nested lists. And while we're going deeper, we should keep the rest elements of the nested list for further processing. Sample list: (list 1 (list 2 3) 4 5)

Until (list 2 3) we have already added 1 to accumulator. Since we can't use stack, we need some other place to store the rest elements of the list. And this place is the lst argument, which contains elements of the original list to be flattened. We can just append the lst to the rest elements (cdr (list 2 3)) which are (list 3), and proceed with the list's head we stumbled upon while flattening, i. e. (car (list 2 3)) which is just 2. Now, (and (list? lst-interm) (not (null? lst-interm))) succeeds because flat-do is called this way:

(flat-do (list 1) (list 2 3) (list 4 5))

and the condition triggers this code:

(flat-do (list 1) (car (list 2 3)) (append (cdr (list 2 3)) (list 4 5)))

flat-do again is called this way: (flat-do (list 1) 2 (list 3 4 5))

The condition (not (list? 2)) now succeeds and the code (flat-do (cons 2 1) empty (list 3 4 5)) is evaluated.

The rest processing is done with else branch until lst is null? and reverse is performed on acc. Function then returns the reversed accumulator.

乄_柒ぐ汐 2024-10-31 04:20:17

我们可以遵循 John McCarthy 的 gopher 技巧的灵感,即通过旋转列表(虽然,这里不是外科手术,而是虚拟的,即使用参数):

(define (flatten lst)
  (flat (reverse lst) empty))

(define (flat lst rs)
  (cond
     ((null? lst) rs)
     ((null? (car lst))
       (flat (cdr lst) rs))
     ((not (pair? (car lst))) ;; atom? (car lst)
       (flat (cdr lst) 
             (cons (car lst) rs)))
     (else
       (flat (append (reverse (car lst))  ;; NB
                     (cdr lst))
             rs))))

仍然存在一个争论点,仍然必须转换为尾递归调用:

(list? a) =>
(flat (append (reverse a) d) rs)         ;; NB
=
(define (flaprev       a  d  rs)
  (cond
    ((null? a)       (flat           d rs))
    ((null? (car a)) (flaprev (cdr a) d rs))
    (else            (flaprev (cdr a)
                         (cons (car a) d) rs))))

巧合的是,这显示了一种转换的通用技术:通过将所有嵌套参数提升到同一级别,将每个非尾部嵌套调用转变为平面调用,然后根据其使用方式、预期产生的结果以及要遵循的规则来实现所需的函数语义。

这种“做这个,然后做那个”的方法通过CPS 技术,在评论中提到:

(define (flatten lst)   ;; your definition
  (cond 
    ((null? lst) '())
    ((list? (car lst)) 
      (append
            (flatten (car lst)) 
                   (flatten (cdr lst))))
    (else 
      (cons (car lst) 
                   (flatten (cdr lst))))))
===
(define (flatten lst)         ;; becomes
  (flat lst (lambda (x) x)))

(define (flat lst c)  ;; continuation "c" = "and then
  (cond               ;;                 use the result as"
    ((null? lst)  (c '()))
    ((list? (car lst))
      (flat (cdr lst) (lambda (rd)
           (flat (car lst) (lambda (ra)
               (c (append ra rd)))))))     ;; NB: append (??)
    (else
      (flat (cdr lst) (lambda (rd)
               (c (cons (car lst) rd)))))))

但是那个讨厌的 append 是怎么回事?我们可以用以下方法消除它
与我们在flaprev中使用的技术相同,通过将所有嵌套参数提升到同一级别,为要反向追加的列表添加显式参数:

(define (flatten lst)
  (flat lst '() (lambda (x) x)))
         ;; ^^^__
         ;;      vvv
(define (flat lst d c)
  (cond  ;;      ___vvv
    ((null? lst)  (c d))
    ((list? (car lst))
      (flat (cdr lst) d (lambda (rd)
           (flat (car lst) rd (lambda (ra)
               (c ra))))))
    (else
      (flat (cdr lst) d (lambda (rd)
               (c (cons (car lst) rd)))))))

现在最长延续的长度是输入中最长的子列表,而不是输出列表的长度。

We can follow the inspiration of John McCarthy's gopher trick, i.e. do it by rotating the list (though, here, not surgically but virtually, i.e. using the arguments for that):

(define (flatten lst)
  (flat (reverse lst) empty))

(define (flat lst rs)
  (cond
     ((null? lst) rs)
     ((null? (car lst))
       (flat (cdr lst) rs))
     ((not (pair? (car lst))) ;; atom? (car lst)
       (flat (cdr lst) 
             (cons (car lst) rs)))
     (else
       (flat (append (reverse (car lst))  ;; NB
                     (cdr lst))
             rs))))

There remains one point of contention which still has to be converted to the tail recursive call:

(list? a) =>
(flat (append (reverse a) d) rs)         ;; NB
=
(define (flaprev       a  d  rs)
  (cond
    ((null? a)       (flat           d rs))
    ((null? (car a)) (flaprev (cdr a) d rs))
    (else            (flaprev (cdr a)
                         (cons (car a) d) rs))))

Coincidentally, this shows a general technique for the conversion: turn every non-tail nested call into a flat call by bringing all the nested arguments up to the same level, then implement the needed function semantics according to how it is used and what's expected of it to produce and which laws to follow.

This "do this, and then do that" approach is formalized with the CPS technique, mentioned in the comments:

(define (flatten lst)   ;; your definition
  (cond 
    ((null? lst) '())
    ((list? (car lst)) 
      (append
            (flatten (car lst)) 
                   (flatten (cdr lst))))
    (else 
      (cons (car lst) 
                   (flatten (cdr lst))))))
===
(define (flatten lst)         ;; becomes
  (flat lst (lambda (x) x)))

(define (flat lst c)  ;; continuation "c" = "and then
  (cond               ;;                 use the result as"
    ((null? lst)  (c '()))
    ((list? (car lst))
      (flat (cdr lst) (lambda (rd)
           (flat (car lst) (lambda (ra)
               (c (append ra rd)))))))     ;; NB: append (??)
    (else
      (flat (cdr lst) (lambda (rd)
               (c (cons (car lst) rd)))))))

But what's with that pesky append? We can eliminate it with the
same technique as we used in flaprev, by bringing all the nested arguments up to the same level, adding an explicit parameter for the list to reverse-append unto:

(define (flatten lst)
  (flat lst '() (lambda (x) x)))
         ;; ^^^__
         ;;      vvv
(define (flat lst d c)
  (cond  ;;      ___vvv
    ((null? lst)  (c d))
    ((list? (car lst))
      (flat (cdr lst) d (lambda (rd)
           (flat (car lst) rd (lambda (ra)
               (c ra))))))
    (else
      (flat (cdr lst) d (lambda (rd)
               (c (cons (car lst) rd)))))))

Now the length of the longest continuation is that of the longest sublist in the input, instead of the length of the output list.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文