其中dash.el
有一个功能-separate
可以完全按照您的要求进行:
(-separate 'evenp '(1 2 3 4)) ; => '((2 4) (1 3))
如果您使用-separate
. 我必须在Elisp中实现 Haskell 的分区函数。Elisp 在许多方面与 Common Lisp 相似1,因此这个答案对两种语言的编码人员都很有用。我的代码受到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
中是命令式的 Common Lisp,它使用广泛的“推倒”O(1)
习语,您首先通过在列表前添加 in和反转 in来生成列表O(n)
。向列表追加一次将是O(n)
由于列表实现为cons 单元格,因此追加n
项目将是O(n²)
. partition-append
说明添加到末尾。由于我是函数式编程reduce
爱好者,因此我使用in编写了无副作用版本partition-reduce-reverse
。
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)
参考
- Common Lisp 和 Emacs Lisp 的区别
- 映射高阶函数
- 功能细胞和价值细胞分离的技术问题
- 折叠高阶函数