Вот способ, который использует только стандартные библиотечные функции и только хвостовые вызовы, хотя он выполняет линейный поиск, чтобы увидеть, был ли элемент уже просмотрен или вставлен в результат:
(define remove-nondup
(λ (ls)
(reverse
(let loop ([ls ls] [found '()] [acc '()])
(cond
[(null? ls)
acc]
[(memq (car ls) found)
(loop (cdr ls)
found
(if (memq (car ls) acc)
acc
(cons (car ls) acc)))]
[else
(loop (cdr ls)
(cons (car ls) found)
acc)])))))
(remove-nondup '(a a a b c c c d e e e e)) =>
(a c e)
(remove-nondup '(a a a b c c c d e e e e f a a f)) =>
(a c e f)
loop
- это «именованное let»: удобный способ вставить вспомогательную процедуру в процедуру без большого количества синтаксических помех.
Если вы хотите только уменьшить последовательных дубликатов до одного элемента и удалять элементы только тогда, когда они не встречаются дважды, то есть способ «запомнить» элемент две ячейки назад без поиска для этого и используя только хвостовые вызовы:
(define remove-nonconsecdup
(λ (ls)
(reverse
(letrec (
[got1 (λ (ls prev acc)
(cond
[(null? ls)
acc]
[(eq? prev (car ls))
(got2 (cdr ls) (cons prev acc))]
[else
(got1 (cdr ls) (car ls) acc)]))]
[got2 (λ (ls acc)
(cond
[(null? ls)
acc]
[(eq? (car acc) (car ls))
(got2 (cdr ls) acc)]
[else
(got1 (cdr ls) (car ls) acc)]))])
(if (null? ls)
'()
(got1 (cdr ls) (car ls) '()))))))
(remove-nonconsecdup '(a a a b c c c d e e e e)) =>
(a c e)
(remove-nonconsecdup '(a a a b c c c d e e e e f a a f)) =>
(a c e a)
Я не люблю переворачивать списки, но вызвать reverse
легко. Если дополнительная обработка, выполняемая reverse
, является проблемой, вы могли бы делать нехвостовые вызовы или вставлять элементы в конец списка, но это сложнее сделать эффективно (но легко с нестандартным библиотечным макросом) .