使用列表和树操作库,您可以通过将点对转换为 2 元素列表,然后将列表展平dash
,可以将 alist 转换为平面列表,这可以通过(下面的 2 个表达式是等效的):(a . b)
(a b)
-mapcat
(-mapcat (lambda (x) (list (car x) (cdr x))) my-alist)
(-flatten (-map (lambda (x) (list (car x) (cdr x))) my-alist))
(apply '-concat (-map (lambda (x) (list (car x) (cdr x))) my-alist))
在 Haskell 中,由于惰性求值,类似的表达式(连续应用各种函数来转换列表)会更加高效和自然。
所以我想利用这个答案作为探索编程概念的机会。当您遍历一个列表并根据其内容构建一些结果时(无论结果如何),它是什么?这是一个折叠!fold 最原始的形式是采用二元函数并将其应用于元素 1 和 2,然后应用于 result 和 3,然后应用于 result 和 4,以此类推。所以折叠接受+
和列表[1,2,3,4]
变为((1+2)+3)+4
。dash
有这样的折叠称为-reduce
:(-reduce '+ '(1 2 3 4)) ; => 10
但是这种折叠是不灵活的:整数列表上的折叠只能返回一个整数,而不是某个任意值。我们需要更一般的弃牌,更好的控制。这种通用折叠使用了一个额外的参数,称为累加器,它在二进制函数内部使用。在每个迭代器上,您可以对列表的元素和累加器做任何事情。函数应用的结果成为下一次迭代的累加器。在dash
这种折叠称为-reduce-from
。我们将空列表作为累加器,将原始 alist 中的每个点对逐个取出,然后将其转换为 2 个新元素,我们将它们附加到二进制函数中的累加器中。还有什么比这更容易的?
(-reduce-from (lambda (acc x) (append acc (list (car x) (cdr x)))) '() my-alist)
但是以这种方式附加列表是低效且惯用的,因为列表在 Lisp 中实现为单链表,所以在末尾添加一个元素或连接列表是 O(n),整个函数在 O(n²) 中工作。Lispers 通常针对列表的开头,但-reduce-from
从左到右遍历列表,这就是结果将被反转的原因。如果我们可以从右到左遍历列表,这样我们就可以将元素转换为累加器。嗯,有一个功能-reduce-r-from
:
(-reduce-r-from (lambda (x acc) (cons (car x) (cons (cdr x) acc))) '() my-alist)
-reduce-r-from
是最有效的版本,因为它只遍历 alist 一次。每次您需要使用折叠来构建一些列表时,您都可能需要-reduce-r-from
. 最后,该dash
库的优点在于它为接受函数作为参数以摆脱语法的函数提供了照应宏版本:lambda
(--mapcat (list (car it) (cdr it)) my-alist)
(-flatten (--map (list (car it) (cdr it)) my-alist))
(apply '-concat (--map (list (car it) (cdr it)) my-alist))
(--reduce-r-from (cons (car it) (cons (cdr it) acc))) '() my-alist)