Common Lisp Задача на lisp
Есть следующая задача:
реализовать функционал, который подсчитывает количество элементов заданного многоуровневого списка, удовлетворяющих некоторому предикату, также передаваемому в качестве параметра.
Мое решение выглядит следующим образом:
(defun count-el (pred l)
(cond
((null l) 0)
((atom l) (if (funcall pred l) 1 0))
(t (+ (count-el pred (car l))
(count-el pred (cdr l))))))
и в принципе, задачу решает корректно со всеми функциями, которые передаются в качестве параметра, кроме null. При этом если в функцию добавить специальную обработку для nil, то она будет ломать логику с другими предикатами. Подскажите, пожалуйста, как можно обработать этот частный случай?
Например:
(count-el #'null '(1 (2 a ()) (()) (6 b (4 5)) 3)) ;выводит 0, а должно выводит 2, так как 2 пустых списка
(count-el #'symbolp '(1 (2 3 c) ((a 9 (4 5)) b))) ;выводит 3, что правильно
(count-el #'oddp '(1 (2 3) ((7 9 (4 5)) 8))) ;вывод 5
Ответы (2 шт):
Проблема решается смещением проверки со всего аргумента l на его первый элемент. Достаточно проверить, что он является атомом, в этом случае можно применять к нему предикат и добить результат для хвоста
(defun count-el (pred l)
(cond
((null l) 0)
((atom (car l)) ; если первый элемент атом
(+
(if (funcall pred (car l)) 1 0) ; считаем, если удовлетворяет предикату
(count-el pred (cdr l)))) ; результат для хвоста
(t (+ (count-el pred (car l)) (count-el pred (cdr l))))
)
)
Для случаев, когда вызов предиката может вызвать исключение, например, при несоответствии типа аргумента, типу параметра, можно добавить соответствующий handler-case
(defun count-el (pred l)
(defun check-with-handle ()
(if
(handler-case (funcall pred (car l)) ;считаем, если удовлетворяет предикату
(error () nil)) ; в случае ошибки возвращем nil
1 0)
)
(cond
((null l) 0)
((atom (car l)) ; если первый элемент атом
(+
(check-with-handle)
(count-el pred (cdr l)))) ; результат для хвоста
(t
(+
(check-with-handle)
(count-el pred (car l))
(count-el pred (cdr l))))
)
)
Я бы решал задачу немного по другому. count-el получает во втором параметре список. Который обрабатывается так
- если список пуст, возвращаем ноль;
- если первый элемент списка соответствует предикату, добавляем единицу к сумме;
- если первый элемент списка сам является списком, вызываем
count-elот него и добавляем к сумме; - вызываем
count-elот хвоста списка и добавляем к сумме.
ignore-errors нужен, так как в моём решении предикат применяется как к спискам, так и к атомам. Не все предикаты позволяют такое. Например oddp не любит списки и падает с ошибкой, которую ignore-errors игнорирует.
(defun count-el (pred l)
(if
(null l)
0
(+
(if (ignore-errors (funcall pred (car l))) 1 0)
(if (listp (car l)) (count-el pred (car l)) 0)
(count-el pred (cdr l)))
)
)
(print (count-el #'null '(1 (2 a ()) (()) (6 b (4 5)) 3))) ; выводит 2
(print (count-el #'symbolp '(1 (2 3 c) ((a 9 (4 5)) b)))) ; выводит 3
(print (count-el #'oddp '(1 (2 3) ((7 9 (4 5)) 8)))) ; выводит 5
(print (count-el #'listp '(1 (2 3) ((7 9 (4 5)) 8)))) ; выводит 4