Recursion 球拍/方案扁平化说明

Recursion 球拍/方案扁平化说明,recursion,scheme,racket,flatten,Recursion,Scheme,Racket,Flatten,有人能帮我准确地分解以下版本的Flatte的执行顺序吗?我在用球拍 版本1,是从球拍本身,而版本2是一个更常见的?实施 (define (flatten1 list) (let loop ([l list] [acc null]) (printf "l = ~a acc = ~a\n" l acc) (cond [(null? l) acc] [(pair? l) (loop (car l) (loop (cdr l) acc))] [

有人能帮我准确地分解以下版本的Flatte的执行顺序吗?我在用球拍

版本1,是从球拍本身,而版本2是一个更常见的?实施

(define (flatten1 list)
  (let loop ([l list] [acc null])
    (printf "l = ~a acc = ~a\n" l acc)
    (cond [(null? l) acc]
          [(pair? l) (loop (car l) (loop (cdr l) acc))]
          [else (cons l acc)])))

(define (flatten2 l)
  (printf "l = ~a\n" l)
  (cond [(null? l) null]
        [(atom? l) (list l)]
        [else (append (flatten2 (car l)) (flatten2 (cdr l)))]))
现在,使用“(1 2 3)运行第一个示例会产生:

l = (1 2 3) acc = ()
l = (2 3) acc = ()
l = (3) acc = ()
l = () acc = ()
l = 3 acc = ()
l = 2 acc = (3)
l = 1 acc = (2 3)
'(1 2 3)
第二种方法产生:

l = (1 2 3)
l = 1
l = (2 3)
l = 2
l = (3)
l = 3
l = ()
'(1 2 3)
执行的顺序似乎不同。在第一个示例中,看起来第二个循环
(循环(cdr l)acc)
在第一个循环之前启动,因为“(2 3)正在立即打印。而在第二个示例中,1在’(2 3)之前打印,这似乎是第一次对append内部的展平调用求值

我正在讨论小阴谋家,但这些是更难的例子,我真的需要一些帮助


非常感谢。

主要区别在于:

  • 1
    将输出元素(首先从
    cdr
    侧,然后从
    car
    侧)存储到累加器中。这是因为列表是从右到左构建的,所以首先在
    cdr
    侧工作是正确的
  • flatte2
    通过递归展平
    car
    cdr
    侧边,然后将它们附加在一起
flatt1
速度更快,尤其是当
car
一侧的树很重时:使用累加器意味着无论发生什么情况,都不会有额外的列表复制。然而,
append
调用
flatt2
会导致复制
append
的左侧,这意味着如果
car
侧的树很重,会有大量额外的列表复制

<> P>因此,我将考虑<代码> FLANT2初学者的扁平化实现,以及<>代码> FLANTE1一个更精湛、专业的版本。另请参见,它使用与
flatt1
相同的原理工作,但使用左折叠而不是
flatt1
使用的右折叠


(左折叠解决方案使用更少的堆栈空间,但可能会使用更多的堆空间。右折叠解决方案使用更多的堆栈,通常使用更少的堆,尽管快速阅读
flatt1
表明,在这种情况下,堆的使用与我的实现大致相同。)

并不是您问题的答案(Chris已经提供了一个很好的答案!),但为了完整起见,这里有另一种实现
flatten
的方法,类似于
flatte2
,但更简洁一些:

(define (atom? x)
  (and (not (null? x))
       (not (pair? x))))

(define (flatten lst)
  (if (atom? lst)
      (list lst)
      (apply append (map flatten lst))))
还有另一种实现左折叠版本的方法(与
flatt1
有更多共同点),使用标准球拍程序:

(define (flatten lst)
  (define (loop lst acc)
    (if (atom? lst)
        (cons lst acc)
        (foldl loop acc lst)))
  (reverse (loop lst '())))

谢谢。但是为什么在示例一中,(loop(cdrl))在(loop(carl))之前触发,而(flatt2(carl))在(flatt2(cdrl))之前触发呢在示例2中,在
flatt1
中,
cdr
位于
car
之前,原因如下:累加器列表是从右到左构建的。在
flatt2
中,顺序无关紧要,但Racket在计算表达式时总是使用从左到右的顺序,因此您可以在
cdr>之前看到
car
这里。好的,谢谢。所以在Racket中,列表(con)是从右到左构建的,但表达式是从左到右求值的。这很有帮助。在所有方案系统中,列表总是从右到左构建的,但子表达式的求值顺序是未指定的(这意味着不同的实现可以使用他们喜欢的任何顺序)。Racket开发人员有意识地决定始终使用从左到右的评估;其他方案实现可能表现出不同的(不一定是确定性的)顺序。最初,我认为flatt1中对循环的调用也应该从左到右求值,但造成我混淆的主要原因是,对循环的第二个调用是对循环的第一个调用的第二个参数,因此第二个循环首先求值,而对flatt2的第二个调用是append的独立参数,等等在评估第一个参数后激发。