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 шт):

Автор решения: Grundy

Проблема решается смещением проверки со всего аргумента 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))))
  )
)

пример online

Для случаев, когда вызов предиката может вызвать исключение, например, при несоответствии типа аргумента, типу параметра, можно добавить соответствующий 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))))
  )
)

второй пример

→ Ссылка
Автор решения: Stanislav Volodarskiy

Я бы решал задачу немного по другому. 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
→ Ссылка