Урок 7
На прошлом уроке, мы рассматривали аналог функции VL-REMOVE
Сегодня я хочу показать аналоги функций:
VL-REMOVE-IF
VL-REMOVE-IF-NOT
VL-POSITION
Рассмотрим VL-REMOVE-IF
Пример стандартного применения:
(setq f (function(lambda (x)(< 8 x 12)))
lst '(5 10 15 20)
) ;_ setq
(vl-remove-if f lst)
;Возвращает:
; '(5 15 20)
А вот и сама рекурсия.
(defun rec-remove-if (f lst)
(cond
((not lst)
nil
)
(((eval f)
(car lst)
)
(rec-remove-if
f
(cdr lst)
) ;_ rec-remove-if
)
(T
(cons
(car lst)
(rec-remove-if
f
(cdr lst)
) ;_ rec-remove-if
) ;_ cons
) ;_ T
) ;_ cond
) ;_ defun
; Вызывать:
(setq f (function(lambda (x)(< 8 x 12)))
lst '(5 10 15 20)
) ;_ setq
(rec-remove-if f lst)
; Вернет '(5 15 20)
Разберем, как она работает.
В первой проверке, как всегда, организуем выход,
на случай пустого списка и возвращаем NIL
((not lst) ; Проверка списка.
nil ; Возвращаемое значение, для пустого списка.
)
Во второй проверке, применяем тестовую функцию
к первому элементу списка. Если тестовая функция вернет
значение, отличное от NIL делаем самовызов рекурсии
со списком без первого элемента.
(((eval f) ; Активируем тестовую функцию.
(car lst) ; Вычисляем первый элемент списка.
) ; Применяем тестовую функцию к первому элементу списка.
(rec-remove-if
f ; Тестовая функция
(cdr lst) ; Список без первого элемента
) ; Самовызов рекурсии с укороченным списком
)
Третья проверка всегда верна (вместо проверки стоит T).
До этой строки программа дойдет только в случае, если мы имеем,
не пустой список с первым элементом, пропущенным тестовой функцией.
Здесь мы добавляем первый элемент списка, к результату рекурсии,
примененной к укороченному списку - без первого элемента.
(T ; проверка - всегда верна
(cons
(car lst) ; Вычисляем первый элемент списка.
(rec-remove-if
f ; Тестовая функция.
(cdr lst) ; Список без первого элемента.
) ; Самовызов рекурсии с укороченным списком.
) ; Добавление первого элемента к результату рекурсии.
) ;_ T
Рассмотрим VL-REMOVE-IF-NOT
Пример стандартного применения:
(setq f (function(lambda (x)(< 8 x 17)))
lst '(5 10 15 20)
) ;_ setq
(vl-remove-if-not f lst)
;Возвращает:
; '(10 15)
А вот и сама рекурсия.
(defun rec-remove-if-not (f lst)
(cond
((not lst)
nil
)
(((eval f)
(car lst)
)
(cons
(car lst)
(rec-remove-if-not
f
(cdr lst)
) ;_ rec-remove-if
)
)
(T
(rec-remove-if-not
f
(cdr lst)
) ;_ rec-remove-if
) ;_ T
) ;_ cond
) ;_ defun
;Вызывать:
(setq f (function(lambda (x)(< 8 x 17)))
lst '(5 10 15 20)
) ;_ setq
(rec-remove-if-not f lst)
;Возвращает:
; '(10 15)
Эта функция очень похожа на предыдущую, разница только
во второй и третьей проверках, действия после проверок
поменялись местами. Надеюсь, вас не затруднит,
самостоятельно разобраться в этой рекурсии.
Рассмотрим VL-POSITION
Пример стандартного применения:
(vl-position 4 '(2 4 6 4))
;Возвращает:
; 1
А вот и сама рекурсия.
(defun rec-position (test lst / rec-position)
(defun rec-position (test lst i)
(cond
((not lst) nil)
((equal test (car lst)) i)
(t (rec-position test (cdr lst) (1+ i)))
) ;_ cond
) ;_ defun
(rec-position test lst 0)
) ;_ defun
;Вызывать:
(setq test 4
lst '(2 4 6 4)
) ;_ setq
(rec-position test lst)
; Вернет 1
А теперь разберем, как работает рекурсия:
Функция REC-POSITION состоит из двух частей.
В первой части мы определяем локальную функцию
с тем же названием но с дополнительным аргументом,
который будем использовать как счетчик.
(defun rec-position (test lst i)
(cond
((not lst) nil)
((equal test (car lst)) i)
(t (rec-position test (cdr lst) (1+ i)))
) ;_ cond
) ;_ defun
Аргументы:
test - Тестовое значение, позицию которого определяем в списке.
lst - Список, в котором ищем позицию тестового значения.
i - Счетчик, при первом вызове устанавливаем на 0 (зеро)
Во второй части, мы делаем вызов, только, что определенной
функции.
(rec-position test lst 0)
Теперь, немного подробнее, рассмотрим внутреннюю функцию.
Как всегда, в первой проверке COND , мы делаем возможность выхода.
Проверяем, что список не пустой. Если пустой - возвращаем NIL
((not lst) ; Проверка списка.
nil ; Возвращаемое значение, для пустого списка.
)
Во второй проверке, мы сверяем тестовое значение
с первым элементом списка. Если они одинаковые,
возвращаем содержимое счетчика.
((equal
test ; Тестовое значение.
(car lst) ; Первый элемент списка.
) ; Сравниваем первый элемент списка и тестовое значение.
i ; Счетчик - возвращаем при равенстве тестовой функции.
)
Третья проверка всегда верна (вместо проверки стоит T).
До этой строки программа дойдет только в случае, если мы имеем,
не пустой список с первым элементом неравным тестовому значению.
Здесь мы делаем самовызов функции, со списком, без первого элемента,
и счетчиком, увеличенным на единицу.
(t ; проверка - всегда верна.
(rec-position
test ; Тестовое значение.
(cdr lst) ; Укороченный список.
(1+ i) ; Счетчик увеличенный на единицу.
) ; Самовызов функции rec-position.
)
Теперь пару слов о переопределении функции.
Можно пользоваться этой рекурсией без внутреннего переопределения,
но тогда, придется каждый раз, указывать начальное значение счетчика,
при вызове.
Например:
(defun rec-position (test lst i)
(cond
((not lst) nil)
((equal test (car lst)) i)
(t (rec-position test (cdr lst) (1+ i)))
) ;_ cond
) ;_ defun
; Аргументы:
(setq test 4
lst '(2 4 6 4)
) ;_ setq
; Вызывать:
(rec-position test lst 0)
Или можно определить две независимые функции,
первая - вызываемая, вторая - вспомогательная.
Например:
(defun rec-position (test lst)
(rec-position-1 test lst 0)
) ;_ defun
(defun rec-position-1 (test lst i)
(cond
((not lst) nil)
((equal test (car lst)) i)
(t (rec-position-1 test (cdr lst) (1+ i)))
) ;_ cond
) ;_ defun
; Аргументы:
(setq test 4
lst '(2 4 6 4)
) ;_ setq
; Вызывать:
(rec-position test lst)
По аналогии с функциями
VL-REMOVE-IF
можно написать аналог для
VL-POSITION
с использованием тестовой функции,
а не значения и возвращением всех позиций списком...
Например функция:
(defun rec-position-list-if (f lst / rec-position-list-if)
(defun rec-position-list-if (f lst i)
(cond
((not lst) nil)
(((eval f) (car lst)) (cons i (rec-position-list-if f (cdr lst) (1+ i))))
(t (rec-position-list-if f (cdr lst) (1+ i)))
) ;_ cond
) ;_ defun
(rec-position-list-if f lst 0)
) ;_ defun
;Вызывать:
(setq f (function minusp)
lst '(5 -10 15 -20)
) ;_ setq
(rec-position-list-if f lst)
; Вернет
; '(1 3)
Все предложенные варианты работают.
Вариант, с переопределением функции,
я предложил с надеждой, что вы его разберете и сможете использовать,
при необходимости...
PS. Хочу сказать пару слов, по поводу компиляции проектов, содержащих рекурсии.
Все нижесказанное относится к AutoCad 2004 - в других версиях не исследовал,
возможно, вы сможете дать рекомендации для других версий.
При компиляции нельзя использовать опции:
"Separate Namespace" (Отдельное именное пространство)
"Optimize and Link" (Оптимизация и связывание)
При их использовании, рекурсии либо не работают, либо работают не корректно.
Причем, это относится только к *.VLX
При использовании *.FAS либо *.LSP - никаких проблем!