使用方案将元素插入循环列表

Insert element to circular list using scheme

我有一个循环列表,例如:#0=(1 2 3 4 . #0#)。

我想做的是在这个列表中插入一个新元素 (x),这样结果就是 #0=(x 1 2 3 4 . #0#)。我一直在尝试使用此代码(x 是循环列表):

(define (insert! elm)
  (let ((temp x))
    (set-car! x elm)
    (set-cdr! x temp)))

不过,我觉得set-cdr!没有像我想要的那样工作。我在这里错过了什么?也许我跑题了?

由于您要将元素添加到循环列表中,因此需要做两件事:

  1. 在包含附加元素的列表的前面插入一个新的 cons 单元格。这很容易,因为您只需执行一个简单的 (cons elm x).

  2. 您还需要修改循环列表的递归部分,使其指向新创建的cons cell,否则循环部分将只包含列表的旧部分。

要执行后者,您需要一种方法来确定循环列表的 "end" 在哪里。这实际上并不存在,因为列表当然是循环的,但是可以通过对列表的每个元素执行 eq? 检查直到找到等于列表头部的元素来确定。

创建一个辅助函数来执行此操作,insert! 的简单实现如下所示:

(define (find-cdr v lst)
  (if (eq? v (cdr lst)) lst
      (find-cdr v (cdr lst))))

(define (insert! elm)
  (set! x (cons elm x))
  (set-cdr! (find-cdr (cdr x) (cdr x)) x))

将元素添加到列表中的最简单方法是修改列表中的汽车,并将列表的 cdr 设置为新的 cons,其汽车为原始汽车列表的第一个元素,其 cdr 是列表的原始尾部:

(define (prepend! x list)                      ; list = (a . (b ...)) 
  (set-cdr! list (cons (car list) (cdr list))) ; list = (a . (a . (b ...)))
  (set-car! list x))                           ; list = (x . (a . (b ...)))

(let ((l (list 1 2 3)))
  (prepend! 'x l)
  (display l))
;=> (x 1 2 3)

现在,这仍然适用于循环列表,因为列表开头的 cons 单元(即对)保持不变,所以 "final" cdr 仍将指向对象是开始。不过,为了测试这一点,我们需要一些函数来创建循环列表并从中采样,因为它们不包含在语言中(据我所知)。

(define (make-circular list)
  (let loop ((tail list))
    (cond
      ((null? (cdr tail))
       (set-cdr! tail list)
       list)
      (else
       (loop (cdr tail))))))

(define (take n list)
  (if (= n 0)
      '()
      (cons (car list)
            (take (- n 1)
                  (cdr list)))))

(display (take 10 (make-circular (list 1 2 3))))
;=> (1 2 3 1 2 3 1 2 3 1)

现在我们可以检查如果我们在循环列表前面添加会发生什么:

(let ((l (make-circular (list 1 2 3))))
  (prepend! 'x l)
  (display (take 15 l)))
;=> (x 1 2 3 x 1 2 3 x 1 2 3 x 1 2)