Lisp 如何编写Push和Pop-in方案?
现在我有Lisp 如何编写Push和Pop-in方案?,lisp,scheme,racket,Lisp,Scheme,Racket,现在我有 (define (push x a-list) (set! a-list (cons a-list x))) (define (pop a-list) (let ((result (first a-list))) (set! a-list (rest a-list)) result)) 但我得到的结果是: Welcome to DrScheme, version 4.2 [3m]. Language: Module; memory limit: 256 m
(define (push x a-list)
(set! a-list (cons a-list x)))
(define (pop a-list)
(let ((result (first a-list)))
(set! a-list (rest a-list))
result))
但我得到的结果是:
Welcome to DrScheme, version 4.2 [3m].
Language: Module; memory limit: 256 megabytes.
> (define my-list (list 1 2 3))
> (push 4 my-list)
> my-list
(1 2 3)
> (pop my-list)
1
> my-list
(1 2 3)
我做错了什么?是否有更好的写入推送的方法,以便在末尾添加元素并弹出,以便从第一个元素删除该元素?您在那里做的是仅在本地修改“队列”,因此结果在定义的范围之外不可用。这是因为在scheme中,所有内容都是通过值传递的,而不是通过引用传递的。方案结构是不变的
(define queue '()) ;; globally set
(define (push item)
(set! queue (append queue (list item))))
(define (pop)
(if (null? queue)
'()
(let ((pop (car queue)))
(set! queue (cdr queue))
pop)))
;; some testing
(push 1)
queue
(push 2)
queue
(push 3)
queue
(pop)
queue
(pop)
queue
(pop)
问题在于,在方案中,数据和数据处理遵循无副作用规则
所以对于一个真正的队列,我们需要可变性,这是我们没有的。因此,我们必须设法绕过它
由于scheme中的所有内容都是通过值传递的,而不是通过引用传递的,因此事物保持局部性且保持不变,没有副作用。因此,我选择创建一个全局队列,这是一种规避这种情况的方法,通过将我们的更改应用于全局结构,而不是传递任何内容
在任何情况下,如果您只需要1个队列,此方法都可以正常工作,尽管它占用大量内存,因为每次修改结构时都要创建一个新对象
为了获得更好的结果,我们可以使用宏来自动创建队列的
a-list
的内容。函数退出后,此变量不再存在cons
创建一个新的cons单元。cons单元由两部分组成,分别称为car
和cdr
。列表是一系列cons单元格,其中每辆车都有一些值,每个cdr指向相应的下一个单元格,最后一个cdr指向nil。当您写入(cons a-list x)
时,这将创建一个新的cons单元格,其中引用了汽车中的a-list
,以及cdr中的x
,这很可能不是您想要的push
和pop
通常被理解为对称操作。当您将某个内容推送到一个列表(用作堆栈)上时,您希望在之后直接弹出此列表时将其返回。由于列表总是在开始时被引用,所以您希望通过执行(cons x a-list)
将其推送到那里推送
宏(使用定义语法
)扩展到(set!(cons))
。否则,您需要将对列表的引用传递给push
功能。pop
也有类似的情况。传递引用可以通过包装到另一个列表中来完成Svante是正确的,使用宏是惯用的方法 这是一个没有宏的方法,但在缺点方面,您不能使用普通列表作为队列。 至少适用于R5R,在导入正确的库后应适用于R6R
(define (push x queue)
(let loop ((l (car queue)))
(if (null? (cdr l))
(set-cdr! l (list x))
(loop (cdr l)))))
(define (pop queue)
(let ((tmp (car (car queue))))
(set-car! queue (cdr (car queue)))
tmp))
(define make-queue (lambda args (list args)))
(define q (make-queue 1 2 3))
(push 4 q)
q
; ((1 2 3 4))
(pop a)
; ((2 3 4))
q
我想你是想实现一个新的目标。这可以通过几种方式完成,但如果您希望在恒定时间O(1)中执行插入和删除操作,则必须保留对队列前面和后面的引用 您可以将这些引用保存在一个容器中,或者像我的示例中那样,封装在一个闭包中 处理堆栈时通常使用术语
push
和pop
,因此我在下面的代码中将它们更改为enqueue
和dequeue
(define (make-queue)
(let ((front '())
(back '()))
(lambda (msg . obj)
(cond ((eq? msg 'empty?) (null? front))
((eq? msg 'enqueue!)
(if (null? front)
(begin
(set! front obj)
(set! back obj))
(begin
(set-cdr! back obj)
(set! back obj))))
((eq? msg 'dequeue!)
(begin
(let ((val (car front)))
(set! front (cdr front))
val)))
((eq? msg 'queue->list) front)))))
这几乎是Scheme中的面向对象编程!您可以将front
和back
视为队列类的私有成员,将消息视为方法
调用约定有点落后,但很容易用更好的API包装队列:
(define (enqueue! queue x)
(queue 'enqueue! x))
(define (dequeue! queue)
(queue 'dequeue!))
(define (empty-queue? queue)
(queue 'empty?))
(define (queue->list queue)
(queue 'queue->list))
(定义(排队!队列x)
(队列'enqueue!x))
(定义(出列!队列)
(排队'出列!))
(定义(空队列?队列)
(队列“空”)
(定义(队列->列表队列)
(队列“队列->列表))
编辑:
正如所指出的,在PLT方案中,对是默认的,这意味着没有
set car代码>和设置cdr代码>。要使代码在PLT方案中工作,必须改用。在标准方案(R4RS、R5RS或R6RS)中,代码应该可以不经修改地工作。这是关于在代码中使用变异的一点:没有必要为此跳转到宏。我现在假设堆栈操作:为了得到一个可以传递和变异的简单值,您只需要在列表周围有一个包装器,其余的代码保持不变(好吧,只是做了一些小的更改,使它能够正确地执行堆栈操作)。在PLT方案中,这正是方框的用途:
(define (push x a-list)
(set-box! a-list (cons x (unbox a-list))))
(define (pop a-list)
(let ((result (first (unbox a-list))))
(set-box! a-list (rest (unbox a-list)))
result))
还请注意,您可以使用begin0
而不是let
:
(define (pop a-list)
(begin0 (first (unbox a-list))
(set-box! a-list (rest (unbox a-list)))))
至于将其转换为队列,您可以使用上述方法之一,但除了Jonas编写的上一个版本之外,解决方案效率非常低。例如,如果您按照Sev的建议进行操作:
(set-box! queue (append (unbox queue) (list x)))
然后复制整个队列——这意味着向队列中添加项的循环将在每次添加时复制所有项,从而为GC生成大量垃圾(考虑在循环中的字符串末尾追加一个字符)。“unknown(google)”解决方案修改列表并在其末尾添加一个指针,这样可以避免生成要收集的垃圾,但仍然效率低下
Jonas编写的解决方案是实现这一点的常用方法——保持一个指向列表末尾的指针。但是,如果您想在PLT方案中执行此操作,则需要使用可变对:mcons
,mcar
,mcdr
,设置mcar代码>,设置mcdr代码>。自4.0版问世以来,PLT中的常用对是不变的。在许多Lispy语言中都可以找到对列表进行操作的push和pop宏:Emacs Lisp、Gauche Scheme、Common Lisp、Chicken Scheme(在miscmacros egg中)、Arc等<
(set-box! queue (append (unbox queue) (list x)))
Welcome to Racket v6.1.1.
> (define-syntax pop!
(syntax-rules ()
[(pop! xs)
(begin0 (car xs) (set! xs (cdr xs)))]))
> (define-syntax push!
(syntax-rules ()
[(push! item xs)
(set! xs (cons item xs))]))
> (define xs '(3 4 5 6))
> (define ys xs)
> (pop! xs)
3
> (pop! xs)
4
> (push! 9000 xs)
> xs
'(9000 5 6)
> ys ;; Note that this is unchanged.
'(3 4 5 6)