这里有五个版本的delete1 和一个版本的delete1!。每个从返回的列表中删除与给定元素相等的第一个元素。后者更适合时间和空间。不使用破坏性函数,除非在考虑正确使用它们的最后部分。
第一个是非尾递归:
(define (delete1a v l)
(let loop ((l l))
(if (null? l) '()
(if (equal? v (car l))
(cdr l)
(cons (car l) (loop (cdr l)))))))
然后第二个和第三个是尾递归的,但效率低下,它们复制的数量超过了需要的数量。例如,如果找不到该项目,则会复制整个列表。
(define (delete1b v l)
(let loop ((l l) (a '()))
(if (null? l) (reverse a)
(if (equal? v (car l))
(append (reverse a) (cdr l))
(loop (cdr l) (cons (car l) a))))))
第三个包含(append (reverse l) m) 的融合,并在到达列表末尾时删除无用的(reverse a),但它仍会在a 中累积列表的副本(reverse a):
(define (append-reverse l m)
(if (null? l)
m
(append-reverse (cdr l) (cons (car l) m))))
(define (delete1c v ls)
(let loop ((l ls) (a '()))
(if (null? l) ls
(if (equal? v (car l))
(append-reverse a (cdr l))
(loop (cdr l) (cons (car l) a))))))
在第四个版本中,累加器被替换为计数器:
(define (delete1d v ls)
(let loop ((l ls) (a 0))
(if (null? l) ls
(if (equal? v (car l))
(append (take ls a) (cdr l))
(loop (cdr l) (+ a 1))))))
当 take 以递归方式实现时,它必须反转它的结果,但是我们已经有一个 append-reverse 那么为什么不使用一个 take-reverse 以相反的顺序返回它的结果,所以 (append (take a b) c) 被 (append (reverse (take-reverse a b)) c) 替换,然后(append-reverse (take-reverse a b) c):
(define (append-reverse l m)
(if (null? l)
m
(append-reverse (cdr l) (cons (car l) m))))
(define (take-reverse l n)
(let loop ((l l) (n n) (a '()))
(if (= n 0) a
(if (null? l) a
(loop (cdr l) (- n 1) (cons (car l) a))))))
(define (delete1e v ls)
(let loop ((l ls) (a 0))
(if (null? l) ls
(if (equal? v (car l))
(append-reverse (take-reverse ls a) (cdr l))
(loop (cdr l) (+ a 1))))))
这样做的结果是遍历列表一次,如果没有找到值,则不进行复制,如果找到了值,则仅在元素被遍历和复制两次之前的列表的前缀。一次在take-reverse,然后一次在append-reverse。
如果允许破坏性函数,则可以使用append-reverse! 而不是append-reverse 将列表前缀的复制安全地减少到一次,如(append-reverse (take-reverse ls a) (cdr l)) 中(take-reverse ls a) 的结果是@987654344 线性使用的副本@。有类型系统可以证明这是安全的:
(define (append-reverse! l m)
(if (null? l)
m
(let ((next (cdr l)))
(begin (set-cdr! l m)
(append-reverse next l)))))
但人们可能会走得更远,定义delete1!:
(define (delete1! v l)
(let loop ((left '()) (right l))
(if (null? right) l
(if (equal? (car right) v)
(if (null? left) (cdr right)
(begin (set-cdr! left (cdr right))
l))
(loop right (cdr right))))))
这会遍历列表一次并且不会复制。