【问题标题】:Insert element to circular list using scheme使用方案将元素插入循环列表
【发布时间】:2015-04-12 18:11:30
【问题描述】:

我有一个循环列表,例如:#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!没有像我想要的那样工作。我在这里想念什么?也许我已经走远了?

【问题讨论】:

    标签: list insert scheme circular-list


    【解决方案1】:

    将元素添加到列表的最简单方法是修改列表的汽车,并将列表的 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 单元格(即对)保持不变,因此“最终”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)
    

    【讨论】:

    • 感谢您的精彩回答!我总是发现解决方案比我想象的方案复杂得多。
    • 你是对的,+1!我很少使用可变列表,所以这个解决方案对我来说并不明显,但这绝对比我的方法好。 :)
    【解决方案2】:

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

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

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

    要执行后者,您需要一种方法来确定循环列表的“末端”在哪里。这实际上并不存在,因为列表当然是循环的,但可以通过对列表的每个元素执行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))
    

    【讨论】:

    • 我认为这可能比它需要的更复杂。由于尾部已经缠绕,因此修改该 cons 的汽车更容易,并在该 cons 之后插入新的 cons。这使得整个操作时间恒定,因为您不需要找到列表的“结尾”。我添加了 an answer 来演示这种方法。
    • 感谢您的精彩回答。但是,我认为我不同意您的 eq 检查。例如,如果你有一个包含两次相同符号的循环列表,会发生什么?那么当它实际上发现第二次出现相同符号时,是否会误导 eq 检查并相信它已经结束?例如:#0=(1 2 3 4 1 2 .#0#)
    • @Br0dskive 不,亚历克西斯的回答不是比较列表的元素;它实际上是在比较作为列表结构一部分的实际 pair 或 cons 单元格。 (cdr lst) 是列表的rest,即另一个列表。所以检查(eq? v (cdr lst))是否为真意味着检查list v是否与lst的rest列表相同。
    • @Joshua Aah,是的,这是有道理的。谢谢你的澄清:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    • 2020-11-16
    • 2022-06-11
    • 1970-01-01
    • 2017-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多