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)
    将其推送到那里

  • IANAS(我不是一个Schemer),但我认为获得所需内容的最简单方法是使
    推送
    宏(使用
    定义语法
    )扩展到
    (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)