Filter 通过谓词将列表筛选为两部分

Filter 通过谓词将列表筛选为两部分,filter,lisp,common-lisp,Filter,Lisp,Common Lisp,我想做什么 (filter-list-into-two-parts #'evenp '(1 2 3 4 5)) ; => ((2 4) (1 3 5)) 其中,根据谓词的计算结果是否为true,将列表拆分为两个子列表。定义这样一个函数很容易: (defun filter-list-into-two-parts (predicate list) (list (remove-if-not predicate list) (remove-if predicate list))) 但是我想

我想做什么

(filter-list-into-two-parts #'evenp '(1 2 3 4 5))
; => ((2 4) (1 3 5))
其中,根据谓词的计算结果是否为true,将列表拆分为两个子列表。定义这样一个函数很容易:

(defun filter-list-into-two-parts (predicate list)
  (list (remove-if-not predicate list) (remove-if predicate list)))

但是我想知道Lisp中是否有一个内置函数可以做到这一点,或者是编写此函数的更好方法?

我认为没有内置函数,并且您的版本是次优的,因为它遍历列表两次,并对每个列表元素调用谓词两次

(defun filter-list-into-two-parts (predicate list)
  (loop for x in list
    if (funcall predicate x) collect x into yes
    else collect x into no
    finally (return (values yes no))))
我返回两个值,而不是其中的列表;这更为惯用(您将使用从返回的多个值中提取
yes
no
,而不是使用解析列表,它消耗更少,速度更快)

更一般的版本是

(defun split-list (key list &key (test 'eql))
  (let ((ht (make-hash-table :test test)))
    (dolist (x list ht)
      (push x (gethash (funcall key x) ht '())))))
(split-list (lambda (x) (mod x 3)) (loop for i from 0 to 9 collect i))
==> #S(HASH-TABLE :TEST FASTHASH-EQL (2 . (8 5 2)) (1 . (7 4 1)) (0 . (9 6 3 0)))

使用
REDUCE

(reduce (lambda (a b)
          (if (evenp a)
              (push a (first b))
            (push a (second b)))
          b)
        '(1 2 3 4 5)
        :initial-value (list nil nil)
        :from-end t)

我认为公共lisp标准中没有分区函数,但是有一些提供了这样一个实用程序(包括文档和文档)

在中,有一个函数完全满足您的要求:

(-separate 'evenp '(1 2 3 4)) ; => '((2 4) (1 3))
如果使用
-separate
,则可以忽略文章的其余部分。我必须在中实现Haskell的函数。Elisp在许多方面与Common Lisp相似,所以这个答案对两种语言的程序员都很有用。我的代码的灵感来自

是一个破坏性函数,它在要列出的元素前面加上前缀。我没有使用Elisp,因为它只添加同一个元素一次。是一个不累积结果的映射函数2。由于Elisp与Common Lisp一样,对函数和变量3有单独的名称空间,因此必须使用来调用作为参数接收的函数。是一个高阶函数4,它接受
:initial value
关键字,允许多种用途。连接可变数量的列表

在代码
分区中,push
是一种命令式的通用Lisp,它使用了一种广泛的习惯用法,首先通过在
O(1)
中为列表添加前缀并在
O(n)
中反转来生成列表。由于列表实现为,因此在列表中追加一次将是
O(n)
,因此追加
n
项将是
O(n²)
<代码>分区追加演示了添加到末尾。作为一名粉丝,我在
partition reduce reverse
中使用
reduce
编写了无副作用版本

Emacs有一个。我对这3个函数运行它。返回的列表中的第一个元素是总秒数。如您所见,添加到列表的速度非常慢,而函数变体是最快的

ELISP> (benchmark-run 100 (-separate #'evenp (number-sequence 0 1000)))
(0.043594004 0 0.0)
ELISP> (benchmark-run 100 (partition-push #'evenp (number-sequence 0 1000)))
(0.468053176 7 0.2956386049999793)
ELISP> (benchmark-run 100 (partition-append #'evenp (number-sequence 0 1000)))
(7.412973128 162 6.853687342999947)
ELISP> (benchmark-run 100 (partition-reduce-reverse #'evenp (number-sequence 0 1000)))
(0.217411618 3 0.12750035599998455)
工具书类

  • 啊,谢谢,我从来不知道你可以返回两个值!与返回列表相比有什么优势?提取多个值似乎有点麻烦。当存在主返回值和在大多数情况下会被丢弃的附加值时,多个值非常有用。与除法相关的数学函数都是这样工作的,将商返回为主值,将余数返回次值。当很难确定哪一个返回值是“primary”时,我发现最好在列表或自描述结构中返回这些值。需要在调用站点使用笨拙的机制处理多个值,而列表可以很容易地传递。模式匹配减少了返回多个值的需要,因为您可以很好地分解对。
    (defun partition-push (p xs)
      (let (trues falses) ; initialized to nil, nil = '()
        (mapc (lambda (x) ; like mapcar but for side-effects only
                (if (funcall p x)
                    (push x trues)
                  (push x falses)))
              xs)
        (list (reverse trues) (reverse falses))))
    
    (defun partition-append (p xs)
      (reduce (lambda (r x)
                (if (funcall p x)
                    (list (append (car r) (list x))
                          (cadr r))
                  (list (car r)
                        (append (cadr r) (list x)))))
              xs
              :initial-value '(() ()) ; (list nil nil)
              ))
    
    (defun partition-reduce-reverse (p xs)
      (mapcar #'reverse ; reverse both lists
              (reduce (lambda (r x)
                        (if (funcall p x)
                            (list (cons x (car r))
                                  (cadr r))
                          (list (car r)
                                (cons x (cadr r)))))
                      xs
                      :initial-value '(() ())
                      )))
    
    ELISP> (benchmark-run 100 (-separate #'evenp (number-sequence 0 1000)))
    (0.043594004 0 0.0)
    ELISP> (benchmark-run 100 (partition-push #'evenp (number-sequence 0 1000)))
    (0.468053176 7 0.2956386049999793)
    ELISP> (benchmark-run 100 (partition-append #'evenp (number-sequence 0 1000)))
    (7.412973128 162 6.853687342999947)
    ELISP> (benchmark-run 100 (partition-reduce-reverse #'evenp (number-sequence 0 1000)))
    (0.217411618 3 0.12750035599998455)