【问题标题】:How to write unzip using foldr in scheme?如何在方案中使用 foldr 编写解压缩?
【发布时间】:2019-03-24 21:34:42
【问题描述】:

这是unzip函数在Scheme中的代码

(define (unzip lst)
  (define (firsts lst)
    (if (null? lst)
        '()
        (cons (caar lst)
              (firsts (cdr lst)))))
  (define (seconds lst)
    (if (null? lst)
        '()
        (cons (cdar lst)
              (seconds (cdr lst)))))
  (list (firsts lst) (seconds lst)))

会给我们这样的输出:

(unzip '((1 . 2) (3 . 4) (5 . 6))) => '((1 3 5) (2 4 6))'

但我很好奇,我如何使用 Scheme foldr 函数实现相同的函数 unzip,有人可以帮忙吗?真的提前谢谢了。

【问题讨论】:

  • 是否可以提及你编程的语言?
  • 本例我使用Scheme编程

标签: recursion functional-programming scheme racket


【解决方案1】:

由于您使用了racket 标签,我将使用for/fold 给出答案,它的语法比foldrfoldl 要好得多。1

for/fold 的语法大致是:

(for/fold <accumulators>
          <iterators>
  <body>)

我们可以使用两个累加器ret1ret2分别存储这两个列表,然后将它们放在累加器#:result的形式中:

([ret1 '()]
 [ret2 '()]
 #:result (list (reverse ret1) (reverse ret2)))

迭代器相当简单:

 ([i (in-list lst)])

最后,body 只需要拆分每一对,并将其附加到累加器中:

(values (cons (car i) ret1)
        (cons (cdr i) ret2))

所以把它们放在一起给出:

(define (unzip lst)
  (for/fold ([ret1 '()]
             [ret2 '()]
             #:result (list (reverse ret1) (reverse ret2)))
            ([i (in-list lst)])
    (values (cons (car i) ret1)
            (cons (cdr i) ret2))))

如预期:

> (unzip '((1 . 2) (3 . 4) (5 . 6))) 
'((1 3 5) (2 4 6))

1至少在我看来,这些东西总是有点主观。

【讨论】:

    【解决方案2】:

    最漂亮的解决方案 - 但没有 foldr/l

    (define (unzip al) (list (map car al) (map cdr al)))
    

    解决方案 1 - 使用 foldr

    (define (unzip lst)
      (foldr (lambda (next-pair acc) (list (cons (car next-pair)
                                                 (car acc))
                                           (cons (cdr next-pair) 
                                                 (cadr acc))))
             '(() ())
             lst))
    

    注意:在foldr - 虽然不直观 - 第二个参数(而不是第一个)是累积结果(因此现在重命名为 acc) - 这是最终结果列表中的第一个和第二个列表- 而p 是列表中的下一对。所以结果列表中的第一个是(car acc),第二个是(cadr acc)。我自己之前误解了它——通过命名lambda 函数p1p2 的参数,认为foldr 作用于lst 的第一对和最后一对。因此,通过解决您的问题,我更好地理解了foldr 的内部lambda 函数中的两个参数的实际含义——一个是累积结果,另一个是输入列表中的下一个元素。但是,现在尝试了foldl:内部lambda 的参数与foldr 中的顺序相同——首先是下一个对,然后是累加结果。

    foldl的解决方案

    (define (unzip lst)
      (foldl (lambda (p acc) (list (append (car acc) (list (car p)))
                                   (append (cadr acc) (list (cdr p)))))
             '(() ())
             lst))
    

    更高效的foldl 解决方案

    (define (unzip lst)
      (let ((res (foldl (lambda (p acc) (list (cons (car p) (car acc))
                                              (cons (cdr p) (cadr acc))))
                        '(() ())
                        lst)))
        (map reverse res)))
    

    而不是appendconssing 结果和最后reverseing 结果内部列表。

    解决方案 2 - 使用 foldr

    效率不高,但有助于解决方案 1 (flatten (list x y)) -> (cons x y)

    (define (unzip lst)
      (foldr (lambda (next acc) (list (flatten (list (car next)
                                                  (car acc)))
                                   (flatten (list (cdr next) 
                                                  (cdr acc)))))
             '(() ())
             lst))
    

    解决方案 3 - 使用 foldr

    效率更低 - 但有助于解决方案 2 (append (list x) (list y)) -> (list x y):

    (define (unzip lst)
      (foldr (lambda (next acc) (list (flatten (append (list (car next)) 
                                                    (list (car acc))))
                                   (flatten (append (list (cdr next)
                                                          (cdr acc))))))
             '(() ())
             lst))
    

    没有foldr/l的尾递归解决方案

    (define (unzip alist)    
        (define (.unzip l acc) ; even numbered l as a given
          (cond ((null? l) (map reverse acc)) ; reverse both inner lists
                (else (.unzip (cddr l) (list (cons (car l) (car acc))
                                             (cons (cadr l) (cadr acc)))))))
        (.unzip (flatten alist) '(() ())))
    

    全部给予:

    > (unzip '((1 . 2) (3 . 4) (5 . 6))) 
    '((1 3 5) (2 4 6))
    

    【讨论】:

      【解决方案3】:

      这个简单的实现不使用fold*、mapreverseflattenappend - 它使用尾调用

      (define (unzip xs (return list))
        (if (empty? xs)
            (return empty empty)
            (unzip (cdr xs)
                   (lambda (l r)
                     (return (cons (caar xs) l)
                             (cons (cdar xs) r))))))
      
      (unzip '((1 . 2) (3 . 4) (5 . 6)))
      ;; '((1 3 5) (2 4 6))
      

      【讨论】:

      • 这很优雅,尽管我怀疑为每次迭代分配闭包的成本可能不会比中间列表和reverse 或通过非尾调用构建上下文有所改进。
      【解决方案4】:

      在 Leif 的回答的基础上,Racket 有更多的形式,例如 for/fold,可以为您处理一些积累的部分。例如:

      #lang racket
      
      (define (unzip lst)
        (for/lists (firsts seconds #:result (list firsts seconds))
                   ([pr (in-list lst)])
          (values (car pr) (cdr pr))))
      
      (unzip '((1 . 2) (3 . 4) (5 . 6)))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-04
        • 1970-01-01
        • 2012-10-14
        相关资源
        最近更新 更多