函数式编程-使用Fold实现扫描(前缀和)

发布于 2024-08-20 16:10:44 字数 1024 浏览 2 评论 0原文

我一直在自学函数式编程,目前正在使用折叠编写不同的高阶函数。我陷入了执行扫描(也称为前缀和)的困境。我使用折叠的地图实现如下所示:

(define (map op sequence)
  (fold-right (lambda (x l) (cons (op x) l))
              nil
              sequence))

我的扫描镜头如下所示:

(define (scan sequence)
  (fold-left (lambda (x y)
               (append x (list (+ y (car (reverse x))))))
             (list 0)
             sequence))

我的观察是“x”是到目前为止的结果数组,“y”是传入列表中的下一个元素。这会产生:

(scan (list 1 4 8 3 7 9)) -> (0 1 5 13 16 23 32)

但这看起来相当难看,结果列表的反转在 lambda 内部进行。我更不想对结果列表进行全局操作,因为我的下一次尝试是尝试并行化其中的大部分内容(这是一个不同的故事,我正在查看几篇 CUDA 论文)。

有人有更优雅的扫描解决方案吗?

顺便说一句,我的左折叠和右折叠的实现是:

(define (fold-left op initial sequence)
 (define (iter result rest)
  (if (null? rest)
   result
   (iter (op result (car rest)) (cdr rest))))
 (iter initial sequence))

(define (fold-right op initial sequence)
 (if (null? sequence)
  initial
  (op (car sequence) (fold-right op initial (cdr sequence)))))

I've been teaching myself functional programming, and I'm currently writing different higher order functions using folds. I'm stuck implementing scan (also known as prefix sum). My map implementation using fold looks like:

(define (map op sequence)
  (fold-right (lambda (x l) (cons (op x) l))
              nil
              sequence))

And my shot at scan looks like:

(define (scan sequence)
  (fold-left (lambda (x y)
               (append x (list (+ y (car (reverse x))))))
             (list 0)
             sequence))

My observation being that the "x" is the resulting array so far, and "y" is the next element in the incoming list. This produces:

(scan (list 1 4 8 3 7 9)) -> (0 1 5 13 16 23 32)

But this looks pretty ugly, with the reversing of the resulting list going on inside the lambda. I'd much prefer to not do global operations on the resulting list, since my next attempt is to try and parallelize much of this (that's a different story, I'm looking at several CUDA papers).

Does anyone have a more elegant solution for scan?

BTW my implementation of fold-left and fold-right is:

(define (fold-left op initial sequence)
 (define (iter result rest)
  (if (null? rest)
   result
   (iter (op result (car rest)) (cdr rest))))
 (iter initial sequence))

(define (fold-right op initial sequence)
 (if (null? sequence)
  initial
  (op (car sequence) (fold-right op initial (cdr sequence)))))

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

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

发布评论

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

评论(3

弃爱 2024-08-27 16:10:44

恕我直言,扫描可以很好地用折叠来表达。

Haskell 示例:

scan func list = reverse $ foldl (\l e -> (func e (head l)) : l) [head list] (tail list)

应该翻译成这样

(define scan
  (lambda (func seq)
    (reverse 
      (fold-left 
       (lambda (l e) (cons (func e (car l)) l))
       (list (car seq))
       (cdr seq)))))

Imho scan is very well expressible in terms of fold.

Haskell example:

scan func list = reverse $ foldl (\l e -> (func e (head l)) : l) [head list] (tail list)

Should translate into something like this

(define scan
  (lambda (func seq)
    (reverse 
      (fold-left 
       (lambda (l e) (cons (func e (car l)) l))
       (list (car seq))
       (cdr seq)))))
总以为 2024-08-27 16:10:44

我不会这样做。 fold 实际上可以通过 scan (扫描列表的最后一个元素)来实现。但scanfold实际上是正交操作。如果您读过 CUDA 论文,您会注意到扫描由两个阶段组成:第一个阶段产生折叠结果作为副产品。第二阶段仅用于扫描(当然,这仅适用于并行实现;如果不依赖于扫描,则fold的顺序实现会更有效)根本没有)。

I wouldn’t do this. fold can actually be implemented in terms of scan (last element of the scanned list). But scan and fold are in fact orthogonal operations. If you’ve read the CUDA papers you’ll notice that a scan consists of two phases: the first yields the fold result as a by-product. The second phase is only used for the scan (of course, this only counts for parallel implementations; a sequential implementation of fold is more efficient if it doesn’t rely on scan at all).

烟织青萝梦 2024-08-27 16:10:44

恕我直言,达里奥通过使用反向进行作弊,因为练习是用折叠而不是反向折叠来表达的。当然,这是一种可怕的表达扫描的方式,但却是将方钉塞入圆孔的有趣练习。

这是在 haskell 中,当然我不懂 lisp

let scan f list = foldl (\ xs next -> xs++[f (last xs) next]) [0] list
scan (+) [1, 4, 8, 3, 7, 9]
[0,1,5,13,16,23,32]

,使用与 Dario 相同的技巧可以摆脱前导 0:

let scan f list = foldl (\ xs next -> xs++[f (last xs) next]) [head list] (tail list)
scan (+) [1, 4, 8, 3, 7, 9]
[1,5,13,16,23,32]

imho Dario cheated by using reverse since the exercise was about expressing in terms of fold not a reverse fold. This, of course, is a horrible way to express scan but it is a fun exercise of jamming a square peg into a round hole.

Here it is in haskell, I don't know lisp

let scan f list = foldl (\ xs next -> xs++[f (last xs) next]) [0] list
scan (+) [1, 4, 8, 3, 7, 9]
[0,1,5,13,16,23,32]

of course, using teh same trick as Dario one can get rid of that leading 0:

let scan f list = foldl (\ xs next -> xs++[f (last xs) next]) [head list] (tail list)
scan (+) [1, 4, 8, 3, 7, 9]
[1,5,13,16,23,32]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文