我想做
(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中是否有内置函数可以做到这一点,或者更好的方法是编写这个函数?
答案 0 :(得分:6)
我认为没有内置版本,你的版本不是最理想的,因为它遍历列表两次并在每个列表元素上调用谓词两次。
(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))))
我返回两个值而不是其列表;这是更惯用的(您将使用multiple-value-bind
从返回的多个值中提取yes
和no
,而不是使用destructuring-bind
来解析列表,它会减少和更快)。
更通用的版本是
(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)))
答案 1 :(得分:5)
使用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)
答案 2 :(得分:2)
在dash.el
中,有一个函数-separate
完全符合您的要求:
(-separate 'evenp '(1 2 3 4)) ; => '((2 4) (1 3))
如果您使用-separate
,则可以忽略帖子的其余部分。我必须在partition中实现Haskell的Elisp函数。 Elisp在很多方面与Common Lisp类似 1 ,所以这个答案对两种语言的编码器都很有用。我的代码受到similar implementations for Python
(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 '(() ())
)))
push
是一个破坏性的函数,它预先列出一个元素。我没有使用Elisp' add-to-list
,因为它只添加了一次相同的元素。 mapc
是一个地图函数 2 ,它不会累积结果。由于Elisp与Common Lisp一样,具有函数和变量 3 的单独命名空间,因此必须使用funcall
来调用作为参数接收的函数。 reduce
是一个高阶函数 4 ,它接受:initial-value
关键字,允许多种用途。 append
连接可变数量的列表。
在代码partition-push
中,使用广泛的"push and reverse"惯用法的命令式Common Lisp,首先通过在O(1)
中的列表前面并在O(n)
中反转来生成列表。由于列表实现为cons cells,因此在列表中追加一次将为O(n)
,因此追加n
项将为O(n²)
。 partition-append
说明了添加到最后。由于我是functional programming粉丝,我在reduce
中使用partition-reduce-reverse
编写了没有副作用的版本。
Emacs有profiling tool。我针对这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)
答案 3 :(得分:1)