使用带有 &key 参数的 remove-if-not

Using remove-if-not with &key parameters

我有以下代码,它应该是一个高阶函数,它根据输入的 &key 参数过滤元素(在本例中为 :year:month:type.

   (defun filter-by (&key year month type)
   "Remove members of the list not matching the given year and/or month and/or type, returns a
   function that takes the list"
     (lambda (lst)
       (remove-if-not #'(lambda (element)
         (when year
           (equalp (local-time:timestamp-year (get-record-date element))
                   year)))
         (when month
           (equalp (local-time:timestamp-month (get-record-date element))
                   month)))
         (when type
           (equalp (get-type element)
                   type))))
       lst)))

问题是,除非使用 all 关键字参数,否则它将始终 return nil,我猜是因为 when 表单在 remove-if-not.

内运行

有没有办法在不求助于多个 cond 语句的情况下完成这项工作? cond 的问题是我必须 特别地 写下所有可能使用的参数组合,这对于 3 个参数是可以的,但是如果将来我想使用其他关键字进行过滤。

Common Lisp 的关键字参数有一个特殊的语法,可以让你知道 是否提供了参数。我认为你应该能够使用 这是为了完成你想要的。

这是一个工作示例,尽管数据表示略有不同 因为我没有你对 local-timeget-record-date 的定义。你 应该能够轻松地使它适应您的代码。

(defun my-filter-by (lst &key
                         (year  nil year-p)   ;; nil is the default
                         (month nil month-p)  ;; year-p/month-p/day-p say whether
                         (day   nil day-p))   ;; the argument was supplied
  (remove-if-not
   (lambda (element)
     (let* ((year-okp (or (not year-p)
                          (equal year (cdr (assoc :year element)))))
            (month-okp (or (not month-p)
                           (equal month (cdr (assoc :month element)))))
            (day-okp (or (not day-p)
                         (equal day (cdr (assoc :day element)))))
            (all-okp (and year-okp month-okp day-okp)))
       all-okp))
   lst))

还有一些例子:

(defparameter *lst* '(((:year . 2000) (:month . :may) (:day . 17))
                      ((:year . 2000) (:month . :may) (:day . 18))
                      ((:year . 2001) (:month . :aug) (:day . 2))
                      ((:year . 2002) (:month . :jan) (:day . 5))))


(my-filter-by *lst*) ;; keeps everything
(my-filter-by *lst* :year 2000) ;; everything from 2000
(my-filter-by *lst* :year 2000 :day 17) ;; only 2000 / may 17