При использовании многих рекурсивных алгоритмов нередко их реализуют с помощью двух процедур: одной для установки начальных условий и второй для выполнения реальной рекурсии, например:
(define (rid-inner li)
(cond
[(empty? li) '()]
[(empty? (rest li)) '()]
[(cons (first li) (rid-inner (rest li)))]))
(define (rid1 L)
(define r (if (empty? L) '() (rest L)))
(rid-inner r))
С помощью (define r (if (empty? L) '() (rest L)))
мы удаляем первый элемент списка; для этого шага рекурсия не нужна. Затем мы определяем ту же процедуру, что и раньше, с другим именем и вызываем ее с нашим новым списком, в котором уже удален первый элемент. Если вы хотите удалить первый элемент, просто удалите первый элемент; не задумывайся об этом :).
В таком языке, как Racket, который допускает замыкания и вложенные процедуры, вам даже не нужно определять обе процедуры в верхней "глобальной" области модуля; просто определите вашу рекурсивную процедуру внутри вашей начальной процедуры и вызовите ее оттуда. Пример:
(define (rid2 L)
(define r (if (empty? L) '() (rest L)))
(define (rid-inner li)
(cond
[(empty? li) '()]
[(empty? (rest li)) '()]
[(cons (first li) (rid-inner (rest li)))]))
(rid-inner r))
Другой, несколько более чистый способ сделать это - использовать именованный let
, который позволяет нам одновременно устанавливать наши начальные условия, создавать именованную процедуру, а затем немедленно вызывать эту процедуру изнутри себя. Мы делаем это так:
(define (rid3 L)
(let rid-inner ([li (if (empty? L) '() (rest L))])
(cond
[(empty? li) '()]
[(empty? (rest li)) '()]
[(cons (first li) (rid-inner (rest li)))])))
Для тех, кто не знаком с Ракетом, Схемой или связанным с ним Лиспом, имя let
в rid3
поначалу может показаться более загадочным, поскольку в действительности оно делает две или три вещи одновременно. Вы можете найти документы для этого здесь . Не обманывайте себя, это работает точно так же, как rid2
. Имя let
существует именно потому, что этот шаблон очень распространен.