现在我有
(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)
我做错了什么?是否有更好的方法来编写push,以便在结尾处添加元素并弹出以便元素从第一个元素中删除?
答案 0 :(得分:7)
这是关于在代码中使用变异的一点:没有必要为此跳转到宏。我现在假设堆栈操作:为了得到一个可以传递和变异的简单值,你需要的只是列表中的一个包装器,其余的代码保持不变(好吧,这是一个微小的改变,使得它可以正确堆叠操作)。在PLT Scheme中,这正是盒子的用途:
(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生成大量垃圾(考虑附加一个字符到循环中字符串的结尾)。 “未知(谷歌)”解决方案修改了列表并在其末尾添加了一个指针,因此它避免了生成垃圾来收集,但它仍然效率低下。
Jonas编写的解决方案是执行此操作的常用方法 - 保持指向列表末尾的指针。但是,如果您想在PLT Scheme中执行此操作,则需要使用可变对:mcons
,mcar
,mcdr
,set-mcar!
,set-mcdr!
。 PLT中通常的对是不可变的,因为版本4.0出来了。
答案 1 :(得分:5)
您只是设置绑定到词法变量a-list
的内容。函数退出后,该变量不再存在。
cons
制作一个新的cons小区。 cons单元由两部分组成,称为car
和cdr
。列表是一系列缺点单元格,其中每个汽车保持一些值,每个cdr指向相应的下一个单元格,最后一个cdr指向nil。当您编写(cons a-list x)
时,会创建一个新的cons单元格,其中包含对汽车中a-list
的引用,以及cdr中的x
,这很可能不是您想要的。
push
和pop
通常被理解为对称操作。当您将某些内容推送到列表(作为堆栈)时,您希望在之后直接弹出此列表时将其恢复。由于列表始终在其开头引用,因此您希望通过执行(cons x a-list)
来推送列表。
IANAS(我不是Schemer),但我认为获得所需内容的最简单方法是使push
成为扩展为{{define-syntax
的宏(使用(set! <lst> (cons <obj> <lst>))
) 1}}。否则,您需要将引用传递到列表中的push
函数。类似于pop
。传递引用可以通过包装到另一个列表中来完成。
答案 2 :(得分:3)
Svante是正确的,使用宏是惯用法。
这是一种没有宏的方法,但是在下方你不能使用普通列表作为队列。 至少与R5RS一起使用,在导入正确的库后应该在R6RS中工作。
(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
答案 3 :(得分:1)
我想您正在尝试实施queue。这可以通过多种方式完成,但如果您希望插入和删除操作都在恒定时间内执行,O(1),则必须保持对前面和后面的引用。队列。
您可以将这些引用保存在cons cell或我的示例中,包含在闭包中。
术语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)))))
make-queue
返回一个过程,该过程将变量front
和back
中的队列状态包装起来。此过程接受将执行队列数据结构过程的不同消息。
此程序可以这样使用:
> (define q (make-queue)) > (q 'empty?) #t > (q 'enqueue! 4) > (q 'empty?) #f > (q 'enqueue! 9) > (q 'queue->list) (4 9) > (q 'dequeue!) 4 > (q 'queue->list) (9)
这几乎是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))
修改强>
正如Eli指出的那样,PLT Scheme默认情况下对immutable,这意味着没有set-car!
和set-cdr!
。要使代码在PLT Scheme中工作,您必须使用mutable pairs。在标准方案(R4RS,R5RS或R6RS)中,代码应该不加修改。
答案 4 :(得分:0)
您正在做的是仅在本地修改“队列”,因此结果在定义的范围之外不可用。这是因为,在方案中,一切都是通过值传递的,而不是通过引用传递的。而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个队列,这个方法可以正常工作,虽然它是内存密集型的,因为你每次修改结构时都要创建一个新对象。
为了获得更好的结果,我们可以使用宏来自动创建队列。
答案 5 :(得分:0)
在列表上运行的push和pop宏可以在许多Lispy语言中找到:Emacs Lisp,Gauche Scheme,Common Lisp,Chicken Scheme(在miscmacros egg中),Arc等。
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)
请注意,即使列表在Racket中不可变,这仍然有效。一个项目是&#34;弹出&#34;从列表中只需调整指针即可。