在Scheme中如何写Push和Pop?

发布于 2024-07-25 20:29:54 字数 504 浏览 5 评论 0原文

现在我有

(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以便将元素添加到末尾并pop以便从第一个删除元素?

Right now I have

(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))

But I get this 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)

What am I doing wrong? Is there a better way to write push so that the element is added at the end and pop so that the element gets deleted from the first?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

单调的奢华 2024-08-01 20:29:54

这是关于在代码中使用突变的一点:不需要为此跳转到宏。 我现在假设堆栈操作:要获得一个可以传递和变异的简单值,您所需要的只是列表周围的包装器,并且代码的其余部分保持不变(好吧,只需进行微小的更改即可它可以正确执行堆栈操作)。 在 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(考虑在循环中将一个字符附加到字符串的末尾)。 “未知(google)”解决方案修改了列表并在其末尾添加了一个指针,因此它避免了生成垃圾来收集,但它仍然是低效的。

Jonas 编写的解决方案是执行此操作的常用方法 - 保留指向列表末尾的指针。 但是,如果您想在 PLT 方案中执行此操作,则需要使用可变对:mconsmcarmcdrset-mcar!set-mcdr!。 自 4.0 版本发布以来,PLT 中的常用对是不可变的。

This is a point about using mutation in your code: there is no need to jump to macros for that. I'll assume the stack operations for now: to get a simple value that you can pass around and mutate, all you need is a wrapper around the list and the rest of your code stays the same (well, with the minor change that makes it do stack operations properly). In PLT Scheme this is exactly what boxes are for:

(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))

Note also that you can use begin0 instead of the let:

(define (pop a-list)
  (begin0 (first (unbox a-list))
    (set-box! a-list (rest (unbox a-list)))))

As for turning it into a queue, you can use one of the above methods, but except for the last version that Jonas wrote, the solutions are very inefficient. For example, if you do what Sev suggests:

(set-box! queue (append (unbox queue) (list x)))

then this copies the whole queue -- which means that a loop that adds items to your queue will copy it all on each addition, generating a lot of garbage for the GC (think about appending a character to the end of a string in a loop). The "unknown (google)" solution modifies the list and adds a pointer at its end, so it avoids generating garbage to collect, but it's still inefficient.

The solution that Jonas wrote is the common way to do this -- keeping a pointer to the end of the list. However, if you want to do it in PLT Scheme, you will need to use mutable pairs: mcons, mcar, mcdr, set-mcar!, set-mcdr!. The usual pairs in PLT are immutable since version 4.0 came out.

烏雲後面有陽光 2024-08-01 20:29:54
  1. 您只是设置绑定到词法变量a-list的内容。 函数退出后,该变量不再存在。

  2. cons 创建一个新的 cons 单元。 cons 单元由两部分组成,分别称为 carcdr。 列表是一系列 cons 单元,其中每辆车都保存一些值,每个 cdr 指向各自的下一个单元,最后一个 cdr 指向 nil。 当您编写 (cons a-list x) 时,这会创建一个新的 cons 单元,并引用汽车中的 a-listx 在 cdr 中,这很可能不是您想要的。

  3. pushpop 通常被理解为对称操作。 当您将某些内容推入列表(充当堆栈)时,您希望在之后直接弹出该列表时将其取回。 由于列表总是在其开头被引用,因此您希望通过执行 (cons x a-list) 将其推送到那里。

  4. IANAS(我不是计划者),但我认为获得你想要的最简单的方法是使 push 成为一个宏(使用 define-syntax)扩展为 (set!(cons))。 否则,您需要将对列表的引用传递给push函数。 类似的情况也适用于 pop。 传递引用可以通过包装到另一个列表中来完成。

  1. You are just setting what is bound to the lexical variable a-list. This variable doesn't exist anymore after the function exits.

  2. cons makes a new cons cell. A cons cell consists of two parts, which are called car and cdr. A list is a series of cons cells where each car holds some value, and each cdr points to the respective next cell, the last cdr pointing to nil. When you write (cons a-list x), this creates a new cons cell with a reference to a-list in the car, and x in the cdr, which is most likely not what you want.

  3. push and pop are normally understood as symmetric operations. When you push something onto a list (functioning as a stack), then you expect to get it back when you pop this list directly afterwards. Since a list is always referenced to at its beginning, you want to push there, by doing (cons x a-list).

  4. IANAS (I am not a Schemer), but I think that the easiest way to get what you want is to make push a macro (using define-syntax) that expands to (set! <lst> (cons <obj> <lst>)). Otherwise, you need to pass a reference to your list to the push function. Similar holds for pop. Passing a reference can be done by wrapping into another list.

失眠症患者 2024-08-01 20:29:54

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

Svante is correct, using macros is the idiomatic method.

Here is a method with no macros, but on the down side you can not use normal lists as queues.
Works with R5RS at least, should work in R6RS after importing correct libraries.

(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
陌上芳菲 2024-08-01 20:29:54

我想您正在尝试实现队列。 这可以通过多种方式完成,但如果您希望插入和删除操作都在恒定时间 O(1) 内执行,则必须保留对 前面和后面 的引用队列。

您可以将这些参考文献保存在 cons cell 或者像我的例子一样,包裹在一个闭包中。

术语 pushpop 通常在处理堆栈时使用,因此我将它们更改为 enqueuedequeue在下面的代码中。

(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 返回一个过程,该过程将队列的状态包装在变量 frontback 中。 该过程接受不同的消息,这些消息将执行队列数据结构的过程。

这个过程可以这样使用:

> (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中的面向对象编程! 您可以将 frontback 视为队列类的私有成员,并将消息视为方法。

调用约定有点落后,但很容易将队列包装在更好的 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))

编辑:

As Eli 指出,对是 在 PLT 方案中默认是不可变的,这意味着没有 set-car!set-cdr! 。 为了让代码在 PLT 方案中工作,您必须使用可变对。 在标准方案(R4RS、R5RS 或 R6RS)中,代码应无需修改即可工作。

I suppose you are trying to implement a queue. This can be done in several ways, but if you want both the insert and the remove operation to be performed in constant time, O(1), you must keep a reference to the front and the back of the queue.

You can keep these references in a cons cell or as in my example, wrapped in a closure.

The terminology push and pop are usually used when dealing with stacks, so I have changed these to enqueue and dequeue in the code below.

(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 returns a procedure which wraps the state of the queue in the variables front and back. This procedure accepts different messages which will perform the procedures of the queue data structure.

This procedure can be used like this:

> (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)

This is almost object oriented programming in Scheme! You can think of front and back as private members of a queue class and the messages as methods.

The calling conventions is a bit backward but it is easy to wrap the queue in a nicer 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))

Edit:

As Eli points out, pairs are immutable by default in PLT Scheme, which means that there is no set-car! and set-cdr!. For the code to work in PLT Scheme you must use mutable pairs instead. In standard scheme (R4RS, R5RS or R6RS) the code should work unmodified.

空城旧梦 2024-08-01 20:29:54

对列表进行操作的push和pop宏在许多Lispy语言中都可以找到:Emacs Lisp、GaucheScheme、CommonLisp、ChickenScheme(在杂项宏蛋中)、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)

请注意,即使列表在球拍。 只需调整指针即可从列表中“弹出”项目。

The push and pop macros, which operate on lists, are found in many Lispy languages: Emacs Lisp, Gauche Scheme, Common Lisp, Chicken Scheme (in the miscmacros egg), Arc, etc.

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)

Note that this works even though lists are immutable in Racket. An item is "popped" from the list simply by adjusting a pointer.

时光磨忆 2024-08-01 20:29:54

您所做的只是在本地修改“队列”,因此结果在定义范围之外不可用。 这是因为,在方案中,所有内容都是按值传递,而不是按引用传递。 而且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 个队列,则此方法可以正常工作,尽管它是内存密集型,因为每次修改结构时都会创建一个新对象。

为了获得更好的结果,我们可以使用宏来自动创建队列。

What you're doing there is modifying the "queue" locally only, and so the result is not available outside of the definition's scope. This is resulted because, in scheme, everything is passed by value, not by reference. And Scheme structures are immutable.

(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)

The problem relies on the matter that, in Scheme, data and manipulation of it follows the no side-effect rule

So for a true queue, we would want the mutability, which we don't have. So we must try and circumvent it.

Since everything is passed by value in scheme, as opposed to by reference, things remain local and remain unchanged, no side-effects. Therefore, I chose to create a global queue, which is a way to circumvent this, by applying our changes to the structure globally, rather than pass anything in.

In any case, if you just need 1 queue, this method will work fine, although it's memory intensive, as you're creating a new object each time you modify the structure.

For better results, we can use a macro to automate the creation of the queue's.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文