【问题标题】:car: contract violation expected: pair? given: () during remove in binary search tree汽车:合同违约预期:对?给定:()在二叉搜索树中删除期间
【发布时间】:2025-12-19 07:15:17
【问题描述】:

我不明白我是如何违反合同的。似乎当我创建一个 bst 时,它并没有创建 2 个空列表,它只有 ()。

这是我的删除方法:

;Returns the binary search tree representing bst after removing x where f and g are predicates as defined in bst-contains.
    (define (bst-remove bst f g x)
      ;if empty return empty
      (cond ((empty? bst) (bst-create-empty)))
      ;else if equal then check right if right is empty then pull from left
      (cond ((g (car bst) x) (cond ((empty? (caddr bst)) (cond ((empty? (cadr bst)) (bst-create-empty))
                                                               (else (car(cadr bst)))))
                                   ;if right isnt empty then remove from left
                                   (else(bst-create (bst-max-right (caddr bst)) (cadr bst) (bst-remove (caddr bst) f g (bst-max-right (caddr bst))))))) 
                             (else (bst-create (car bst) (bst-remove (cadr bst) f g x) (bst-remove (caddr bst) f g x)))))

我的 bst-create 和 bst-create-empty:

;Returns an empty binary search tree.
(define (bst-create-empty)
  '())

;Returns a binary search tree having the specified root value, with left-subtree left and right-subtree right.
(define (bst-create root left right)
  (list root left right))

我给它的代码是

(bst-remove (bst-create 5 (bst-create 6 (bst-create-empty) (bst-create-empty)) (bst-create 4 (bst-create-empty) (bst-create-empty))) < = 6)

我得到的错误是汽车:预期违反合同:对?给定:()

【问题讨论】:

    标签: scheme


    【解决方案1】:

    你有 Scheme,尤其是 cond 都错了。如果您在一个过程的主体中有两个语句,例如:

    (define (test lst)
      first-expression
      tail-expression)
    

    很明显tail-expression 将遵循first-expression 的任何结果的评估和丢弃。此外,除非first-expression 有副作用,否则它是死代码。您的cond 表达式(cond ((empty? bst) (bst-create-empty))) 是死代码,因为无论结果如何,它都不会成为结果的一部分,因为Scheme 将无条件地评估第二个cond。它会引发错误的(car bst)

    The correct way to have multiple returns are by one expression:
    
    (cond 
      (test1 consequent1)
      (test2 consequent2)
      (test3 consequent3)
      (else alternative))
    

    不用说之前所有的测试都是阴性的,所以如果test3 是真的,那么你知道test1test2 都有阴性结果。您还知道,如果评估了 consequent1,则不会评估其他术语或测试。它停在第一个阳性。

    在您的特定情况下,代码可能如下所示:

    (define (bst-remove bst f g x)
      (cond ((empty? bst)
             (bst-create-empty))
            ((not (g (car bst) x))
             (bst-create (car bst) (bst-remove (cadr bst) f g x) (bst-remove (caddr bst) f g x)))
            ((not (empty? (caddr bst)))
             (bst-create (bst-max-right (caddr bst)) (cadr bst) (bst-remove (caddr bst) f g (bst-max-right (caddr bst)))))
            ((empty? (cadr bst))
             (bst-create-empty))
            (else
             (caadr bst))))
    

    使用嵌套的if 也可以,但是就像嵌套的cond 一样,代码更难阅读。请注意,我否定了一些测试,因为它们只有 一个 替代方案,但随后有几个测试。通过否定,我可以得到一个结果并继续在同一 cond 中测试其他情况。

    【讨论】:

      【解决方案2】:

      您在评论中将您的第二个cond 称为else if,但这不是一个else-if,它只是一个if。也就是说,即使第一个条件为真,您也要检查第二个条件,在这种情况下,条件的 car 部分会导致此错误。

      要解决此问题,您应该将这两个条件作为单个 cond 的一部分,在这种情况下,它实际上会像 else if 一样工作。

      【讨论】:

        【解决方案3】:

        您似乎误解了cond,或者可能是一般的Scheme。

        函数应用程序的结果是函数体中最后一个表达式的值(拥有多个表达式的唯一原因是如果您正在执行具有副作用的操作),并且会评估 cond 表达式与其他 Scheme 表达式完全相同。

        所以表达式(cond ((empty? bst) (bst-create-empty))) 不会返回一棵空树,而是对其求值并生成一棵空树,然后丢弃该树。
        然后评估继续下一个cond,当树为空时这是一个坏主意。

        另一个问题是该函数应该生成一棵树,但(car (cadr bst)) 不会。

        如果你定义了一些有用的访问器函数:

        (define bst-value car)
        (define bst-left cadr)
        (define bst-right caddr)
        

        那么这些行更明显是错误的:

        (cond ((empty? (bst-left bst)) (bst-create-empty))
               (else (bst-value (bst-left bst)))))
        

        修复它,整个表达式变得(合理地)清晰

        (cond ((empty? (bst-left bst)) (bst-create-empty))
               (else (bst-left bst))))
        

        等价于

        (bst-left bst)
        

        现在你有

        (cond ((empty? (bst-right bst)) (bst-left bst))
              ( else make a tree...
        

        但是这里缺乏对称性;当然,如果左子树为空,则结果应该是整个右子树以类似的方式。

        所以,

        (cond ((empty? (bst-right bst)) (bst-left bst))
               (empty? (bst-left bst)) (bst-right bst))
               ( else make a tree...
        

        但是现在我们可以发现另一个问题:即使在这些情况下,我们也需要在完成之前递归到子树中。

        我将在这里离题,因为太多的访问器重复和conds 使代码非常难以阅读。

        使用几个lets(并去掉未使用的f参数),我最终得到了这个:

        (define (bst-remove tree g x)
          (if (empty? tree)
              (bst-create-empty)
              ;; If the tree isn't empty, we need to recurse.
              ;; This work is identical for all the cases below, so
              ;; lift it up here.
              (let ([new-left  (bst-remove (bst-left tree) g x)]
                    [new-right (bst-remove (bst-right tree) g x)])
                ;; Build an appropriate tree with the new subtrees.
                (if (g (bst-value tree) x)
                    (cond [(empty? new-left) new-right] ;; If either new subtree is empty,
                          [(empty? new-right) new-left] ;;  use the other.
                          ;; The complicated case. Get the new node value from the
                          ;; right subtree and remove it from there before using it.
                          [else (let ([new-value (bst-max-right new-right)])
                                   (bst-create new-value
                                               new-left
                                               (bst-remove new-right g new-value)))])
                    ;; The straightforward case.
                    (bst-create (bst-value tree) 
                                 new-left 
                                 new-right)))))
        

        【讨论】: