5

我在 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 st)通过尾部的递归悬挂将 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。

4

1 回答 1

5

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

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

在麻省理工学院计划中工作。

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

(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-附加,n-访问的使用模式,它看起来像一个二次复杂度。

在 Haskell 上下文中这个问题的经典论文是“为什么差异列表比常规连接更有效? ”。您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
于 2013-07-02T14:15:39.383 回答