我需要以非破坏性方式从列表中删除第一个元素。
根据Guile Reference Manual,有一组函数以破坏性方式执行(delq1!,delv1!,delete1!)。另一方面,非破坏性版本会删除元素的所有出现。
我知道我可以编写一个函数(可能是通过过滤器)来完成几行,但我想知道是否有更好/标准的方法来完成它。
作为示例,给出列表
((1 2) (3 4) (1 2))
删除元素时
(1 2)
我希望结果是
((3 4) (1 2))
而原始列表仍然是
((1 2) (3 4) (1 2)).
提前致谢!
答案 0 :(得分:2)
在Scheme中,标准解决方案是创建一个新列表,但没有该元素。在documentation中,我们看到delete
过程消除了所有出现的元素 - 因此我们必须推出自己的解决方案,但这很简单:
(define (delete-1st x lst)
(cond ((null? lst) '())
((equal? (car lst) x) (cdr lst))
(else (cons (car lst)
(delete-1st x (cdr lst))))))
例如:
(delete-1st '(1 2) '((1 2) (3 4) (1 2)))
=> '((3 4) (1 2))
答案 1 :(得分:1)
以下是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)
的结果是由(append-reverse r (cdr l))
线性使用的副本。有类型系统可以证明这是安全的:
(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))))))
这会使列表走一次而不进行复制。