如何在此处反转谓词?

问题描述

我有以下过滤程序:

; (2) filter
(define (filter test sequence)
  ; return a list of the elements that pass the predicate test
  (let ((elem (if (null? sequence) nil (car sequence)))
        (rest (if (null? sequence) nil (cdr sequence))))
    (cond ((null? sequence) nil)
          ((test elem) (cons elem (filter test rest)))
          (else (filter test rest)))))

这是一个使用它返回列表中偶数元素的示例:

(define even? (lambda (x) (= (modulo x 2) 0)))
(define sequence '(1 2 3 4 5 8 9 11 13 14 15 16 17))
(filter even? sequence)
; (2 4 8 14 16)

是否有一种简单的方法可以使用 not 测试来反转选择?例如,我认为以下可能有效:

(filter (not even?) sequence)

但它返回一个错误。我当然可以单独定义 odd

(define odd?  (lambda (x) (not (even? x))))

但我尽量不这样做。有没有办法编写 odd 过程而不直接定义它,而是直接使用 not,就像我上面尝试做的那样?

解决方法

Common Lisp 中有一个 complement 函数可以完成我认为您正在寻找的功能。 complement 是一个高阶过程,它以过程为参数,并返回一个过程,该过程与输入过程采用相同的参数并执行相同的操作,但返回的真值相反。

Racket 有一个类似的过程,negate,在 Scheme 中很容易实现:

(define (complement f)
  (lambda xs (not (apply f xs))))
> (filter even? '(1 2 3 4 5))
(2 4)
> (filter (complement even?) '(1 2 3 4 5))
(1 3 5)
> (> 1 2 3 4 5)
#f
> ((complement >) 1 2 3 4 5)
#t

在球拍中:

scratch.rkt> (filter even? '(1 2 3 4 5))
'(2 4)
scratch.rkt> (filter (negate even?) '(1 2 3 4 5))
'(1 3 5)
scratch.rkt> (> 1 2 3 4 5)
#f
scratch.rkt> ((negate >) 1 2 3 4 5)
#t
,

对此的一般答案是简单地组合 not 和您关心的函数。 Racket 有一个 compose 函数可以执行此操作,但您可以轻松地自己编写一个简单的函数:

(define (compose-1 . functions)
  ;; simple-minded compose: each function other than the last must
  ;; take just one argument; all functions should return just one
  ;; value.
  (define (compose-loop fns)
    (cond
      ((null? fns)
       (λ (x) x))
      ((null? (cdr fns))
       (car fns))
      (else
       (λ (x) ((car fns) ((compose-loop (cdr fns)) x))))))
  (compose-loop functions))

当然要让它更高效、更通用,需要做更多的工作。

然后你可以定义odd?(当然已经定义了):

(define odd? (compose-1 not even)

或者实际上定义一个更通用的 CL 风格的 complement 函数:

(define (complement f)
  (compose-1 not f))
,

一种选择是编写一个 invert 函数,该函数将继续执行(因此初始函数仍然接受一个参数)直到最终评估发生:

(define invert (lambda (func) (lambda (x) (not (func x)))))
(define sequence '(1 2 3 4 5 6 8 9 11 13 14 15 16 17))
(filter (invert even?) sequence)
; (1 3 5 9 11 13 15 17)