展平列表列表

时间:2013-10-07 16:02:42

标签: functional-programming scheme

我是Scheme和函数式编程的新手。有人可以解释这段代码 - 特别是konsknil是什么?目标是压缩列表列表。

(define (fold1 kons knil lst)  
  (if (null? lst)  
      knil  
      (fold1 kons (kons (car lst) knil) (cdr lst))))

我非常肯定kons是一个函数,因为它被应用于两个参数,但仍然不能完全确定它的功能。

3 个答案:

答案 0 :(得分:5)

这是一个(奇怪的)折叠

这是一种广义的折叠程序。在Lisps中,列表由cons单元格和空列表表示,其中每个(正确的)列表是空列表(),或者car是列表元素的cons单元格,其{ {1}}是列表的其余部分。例如,

可以生成列表cdr
(1 2 3 4 5)

您展示的(cons 1 (cons 2 (cons 3 (cons 4 (cons 5 '()))))) 功能:

fold1

是一种获取如上所示的列表并将其转换为:

的方法
(define (fold1 kons knil lst)
  (if (null? lst)
      knil
      (fold1 kons (kons (car lst) knil) (cdr lst))))

这是fold。这是许多操作的有效概括。例如,如果您将(kons 5 (kons 4 (kons 3 (kons 2 (kons 1 knil))))) 用作0knil用作+,则可以计算列表中元素的总和。

通常折叠是右或左关联的。适当的左关联折叠将转换为

kons

使用(kons (kons (kons (kons (kons knil 1) 2) 3) 4) 5) 和中缀符号查看时可能更清楚:

+

正确的关联折叠将成为

(((((0 + 1) + 2) + 3) + 4) + 5)

左关联折叠可以更有效,因为自然实现是尾递归的,并且按照从列表中提取它们的顺序从列表中消耗元素。例如,在正确的左关联示例中,可以首先评估(1 + (2 + (3 + (4 + (5 + 0))))) 以生成一些值(kons knil 1),然后,在相同的堆栈空间中,可以评估v,依此类推。右关联方法需要首先遍历列表的末尾。天真的实现需要堆栈空间与列表的长度成比例。

这个(kons v 2)混合了一些东西,因为它以左关联的方式处理列表的元素,但是组合函数的参数的 order 是相反的。< / p>

只要您拥有代数数据类型,就可以使用此类定义。由于Lisp中的列表是空列表,或者元素和列表与cons结合,您可以编写一个处理这些情况的函数,并通过组合“替换”fold1来生成新值函数和带有一些指定值的空列表。

展平列表列表

所以,如果你有一个列表列表,例如cons,它是由

构建的
((a b) (c d) (e f))

使用右关联折叠,将其转换为:

(cons '(a b) (cons '(c d) (cons '(e f) '())))

(append '(a b) (append '(c d) (append '(e f) '()))) 使用appendkons使用'()。但是,在这个略微混合的折叠中,您的结构将是

knil

所以(kons '(e f) (kons '(c d) (kons '(a b) knil))) 仍然可以knil,但'()需要是一个调用kons的函数,但是交换参数顺序:

append

所以我们有:

(define (flatten lists)
  (fold1 (lambda (right left)
           (append left right))
         '()
         lists))

扁平化更深层列表

鉴于这是(flatten '((a b) (c d) (e f))) ;=> (a b c d e f) 练习,我希望列表列表只嵌套一层。但是,因为我们已经看到了如何实现简单的fold

flatten

我们可以修改它以确保更深层次的列表也被展平。现在(define (flatten lists) (fold1 (lambda (right left) (append left right)) '() lists)) 功能

kons

只需将两个列表附加在一起即可。 (lambda (right left) (append left right)) 是我们已经建立的已经附加和展平的列表。 left是我们现在正在采用的新组件。如果我们打电话给right那也应该压扁任意嵌套列表:

flatten

几乎正确,但现在当我们拨打(define (flatten lists) (fold1 (lambda (right left) (append left (flatten right))) ; recursively flatten sublists '() lists)) 时,我们最终会拨打(flatten '((a b) (c d))),然后拨打电话给(flatten '(a b)) (flatten 'a),但flattenfold1的包装,fold1期望其参数为列表。我们需要决定在使用非列表调用flatten时要执行的操作。一种简单的方法是让它返回一个包含非列表参数的列表。该返回值将与接收值的附加值很好地匹配。

(define (flatten lists)               ; lists is not necessarily a list of lists anymore, 
  (if (not (pair? lists))             ; perhaps a better name should be chosen
      (list lists)
      (fold1 (lambda (right left)
               (append left (flatten right)))
             '()
             lists)))

现在我们有了

(flatten '(a (b (c)) (((d)))))
;=> (a b c d)

答案 1 :(得分:2)

显示的程序是fold的实现:

  

在函数式编程中,fold-也称为reduce,accumulate,aggregate,compress或inject - 指的是一系列高阶函数,它们分析递归数据结构并通过使用给定的组合操作重新组合结果递归处理其组成部分,建立一个返回值

注意:

  • kons参数是一个双参数函数,用于将正在处理的列表的当前元素与累计值“组合”
  • knil参数是累计输出结果

要想看看它是如何工作的,想象一下我们有这样的功能:

(define (reverse knil lst)
  (if (null? lst)
      knil
      (reverse (cons (car lst) knil) (cdr lst))))

(reverse '() '(1 2 3 4))
=> '(4 3 2 1)

在上面knil用于累积结果,它以'()的值开头,因为我们正在构建一个列表作为输出。 kons称为cons,用于构建列表。让我们看另一个例子:

(define (add knil lst)
  (if (null? lst)
      knil
      (add (+ (car lst) knil) (cdr lst))))

(add 0 '(1 2 3 4))
=> 10

在上面knil用于累积结果,它以值0开头,因为我们正在构建一个数字作为输出。 kons被称为+,它会添加数字。

到目前为止,您必须意识到两个示例共享解决方案的相同结构,两者都使用输入列表,唯一改变的是我们如何“组合”从列表中提取的值和起始累计值。如果我们很聪明,我们可以将更改为更高阶程序的部分分解出来,将更改的部分作为参数接收 - 因此fold1诞生了:

(define (fold1 kons knil lst)
  (if (null? lst)
      knil
      (fold1 kons (kons (car lst) knil) (cdr lst))))

上述两个例子都可以用fold1轻松表达,只需传递正确的参数:

(define (reverse lst)
  (fold1 cons '() lst))

(define (add lst)
  (fold1 + 0 lst))

现在问题的第二部分:如果您要使用fold1拼合列表,可以尝试this

(define (helper x lst)
  (if (pair? x)
      (fold1 helper lst x)
      (cons x lst)))

(define (flatten lst)
  (reverse (helper lst '())))

(flatten '(1 2 (3) (4 (5)) 6))
=> '(1 2 3 4 5 6)

答案 2 :(得分:0)

以下代码使用'named let''for'循环可用于展平自身可能列出的元素列表:

(define (myflatten ll)
  (define ol '())
  (let loop ((ll ll))
    (for ((i ll))
      (if (list? i)
          (loop i)
          (set! ol (cons i ol)))))
  (reverse ol))


(myflatten '(a () (b e (c)) (((d))))) 

输出:

'(a b e c d)

但是,它使用'set!',这通常不是首选。

'for'循环也可以用'named let'递归代替:

(define (myflatten ll) 
  (define ol '())
  (let outer ((ll ll))
    (let inner ((il ll))
      (cond
        [(empty? il)]
        [(list? (first il))
         (outer (first il))
         (inner (rest il))]
        [else
         (set! ol (cons (first il) ol))
         (inner (rest il))])))
  (reverse ol))