【问题标题】:Is this simiple purely functional queue valid?这个简单的纯功能队列有效吗?
【发布时间】:2013-06-29 07:41:09
【问题描述】:

我在 Lisp (Scheme) 中开发了一个纯函数式队列,如下所示:

;Internal functions
(define (delay-cons x s)
   (cons x (lambda () s)))
(define (delay-car s)
  (car s))
(define (delay-cdr s)
  ((cdr s)))

(define (delay-append s t)
  (if (null? s)
      t
      (delay-cons (delay-car s) (delay-append (delay-cdr s) t))))

;API
(define (enqueue x q) (delay-append q (delay-cons x empty)))
(define dequeue delay-cdr)
(define peek delay-car)
(define empty '())
(define empty? null?)

delay-cons 类似于 cons,但它通过将尾部包装在闭包中来暂停对尾部的评估。 delay-append 类似地(delay-append s t)通过尾部的递归悬挂将 t 附加到 s。

因此,每个入队都包裹一层闭包,使其成为 O(1),每个 peek 只需检索一个使其成为 O(1) 的值,每个出队检索并评估一个闭包,使其成为 O(1)。

我在其他地方没见过这个;例如,在 Okasaki 的 Purely Functional Data Structures 中,最简单的队列是银行家队列,它比这复杂得多,并且只有分期 O(1) 的入队、窥视和出队。这让我怀疑我的推理有误。

这个数据结构合理吗?在某处有它的参考吗?

编辑: delay-cons 在 delay-append 中使用是错误的;我正在尝试使用宏之类的函数(感谢 Will Ness)。

我尝试使用

来纠正它
(define (delay-append s t)
  (if (null? s)
      t
      (cons (delay-car s) (lambda () (delay-append (delay-cdr s) t)))))

但这不适用于 API。

【问题讨论】:

  • 也许你应该提出断言、测试和示例来检查你做了什么。特别是不清楚这提供了什么样的“延迟”以及“延迟-cons”实际上做了什么——考虑到Scheme做了严格评估。
  • 您的编辑破坏了它。之前还可以。
  • 谢谢威尔,它做到了;我试图弥补延迟问题的错误(我试图使用宏之类的函数) - 入队是 O(N) 所写的。
  • 不。再次打破它。 delay-cons 不会延迟任何事情,因为它是一个函数,所以在评估它的主体之前计算它的参数。你的新delay-append 很简单append
  • 原定义有效:(define x (delay-append (delay-cons 1 ()) (delay-cons 2 ()))) => ;Value: x -- (delay-car x) => ;Value: 1 -- (delay-car (delay-cdr x)) => ;Value: 2 -- (delay-cdr (delay-cdr x)) => ;Value: ()

标签: data-structures functional-programming scheme queue purely-functional


【解决方案1】:

首先,delay-cons 不能是一个函数。它必须是一个宏。 For instance,

(define-syntax s-cons
  (syntax-rules ()
    ((s-cons h t) (cons h (lambda () t))))) 

在 MIT 计划中工作。

但是你可以通过在你的delay-append中使用delay-cons来解决这个问题:

(define (delay-append s t)
  (if (null? s)
      t
      (cons (delay-car s) (lambda () (delay-append (delay-cdr s) t)))))

所以没关系。

至于复杂性,delay-append 并非没有成本。它环绕原始队列。想象一下它有 30 个元素;然后你再追加10个,一个一个。现在,原件被包裹在 10 层 delay-append 中,必须导航到这 30 个元素中的每一个(实际上是 29 个,因为头部被拉到直接的 car 中,由 delay-append 拉出)。所以对于n-appended、n-accessed 的使用模式,它看起来像一个二次复杂度。

在 Haskell 上下文中这个问题的经典论文是“Why are difference lists more efficient than regular concatenation?”。您的delay-append 类似于那里的“常规连接”:

[]  ++ t = t
s   ++ t = (head s) : ((tail s) ++ t)

这是一个插图:

(define (wrap x) (cons x (lambda () () ))) 
(define (decdr s) ((cdr s))) 
(define (app s t) (if (null? s) t
                   (cons (car s) (lambda () (app (decdr s) t)))))

;; RIGHT NESTING
(app (wrap 1) (app (wrap 2) (app (wrap 3) (wrap 4))))  == 

(app #A=#[1 . (\->())] 
     (app #B=#[2 . (\->())] 
          (app #C=#[3 . (\->())] #D=#[4 . (\->())] )))  ==

(app #A# (app #B# 
              #E=#[3 . (\-> (app (decdr #C#) #D#)  )]  ))  ==

(app #A# #F=#[2 . (\-> (app (decdr #B#) #E#))] )  ==

#G=#[1 . (\-> (app (decdr #A#) #F#))]     ;; the return value

;; NOW, (car #G#) is O(1), but what about (decdr #G#)?

(decdr #G#) == (app (decdr #A#) #F#) 
            == (app () #F#) 
            == #F#   ;;  O(1) steps as well

;; LEFT NESTING 

(app (app (app (wrap 1) (wrap 2)) (wrap 3)) (wrap 4))  ==

(app (app (app #D=#[1 . (\->())] #C=#[2 . (\->())] ) 
          #B=#[3 . (\->())] ) 
     #A=#[4 . (\->())] )  == 

(app (app #E=#[1 . (\-> (app (decdr #D#) #C#))] #B#) #A#) == 

(app #F=#[1 . (\-> (app (decdr #E#) #B#))] #A#) == 

#G=#[1 . (\-> (app (decdr #F#) #A#))]       ;; the return value

;; NOW, (car #G#) is O(1), but what about (decdr #G#)?

(decdr #G#) == (app (decdr #F#) #A#) 
            == (app (app (decdr #E#) #B#) #A#)
            == (app (app (app (decdr #D#) #C#) #B#) #A#) 
            == ...   ;; O(N) steps, re-creating the left-nesting structure

【讨论】:

  • 好吧,我明白了:每个入队都需要固定数量的操作,但出队必须解开一整层附加物以推动嵌入在闭包底部的下一个数字,然后重新包装它,所以它是 O(N)。
  • @EdwardRoss cf。切线相关ideone.com/Si5axU。它是纯函数式 O(1) 队列(在简单的 Haskell 中),但它自己它的新元素,而不是让你将它们推到它上面。
猜你喜欢
  • 1970-01-01
  • 2012-11-24
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
  • 2014-01-08
  • 1970-01-01
  • 2012-08-09
  • 2021-06-22
相关资源
最近更新 更多