6

鉴于 Lisp 中代码和数据的“一切皆有清单”的同音方法,我很难理解如何在 Emacs Lisp 中操作 alist。特别是,如何将 alist 转换为两个元素列表的普通列表,反之亦然?似乎 alists 是他们自己的一种特殊类型,不能像常规列表那样被操纵。

具体示例:我正在尝试在我的.emacs文件中绑定一个键,以使用以编程方式生成的名称创建一定大小的框架,如下所示:

(make-frame '((name . "Blarg") (width . 80) (height . 24)))

只要“Blarg”是一个常量字符串,这就可以正常工作,但是由于 alist 开头的引号,我不能放置任何计算结果为字符串的代码来代替“Blarg”。我想做的是通过consing 符号和整数的宽度和高度来建立一个列表,然后add-list将名称放在前面,然后将整个内容传递给make-frame. 但是如何将生成的数据结构转换为 alist?

关于如何make-frame做我想做的事的具体答案当然会受到赞赏,但正如我的标题所示,我希望对如何操作 alists 并将它们转换为/从常规列表进行更一般的解释。

4

4 回答 4

6

只是我在构建 alist 上的两分钱:

(pairlis '(name width height)
         '("Blarg" 80 24))
;; => ((name . "Blarg") (width . 80) (height . 24))
于 2013-11-04T19:20:46.337 回答
5

@wvxvw 回答了您的一般问题。但请注意,所描述的并不是 alist 转换为列表,反之亦然。

通常,您不需要进行任何此类“转换”。相反,您可以直接使用 alist —— 毕竟它们是列表。

这让我回答了你的make-frame问题,这就是foobar你想要的名字:

(make-frame `((name . ,foobar) (width . 80) (height . 24)))

或者,如果您愿意:

(make-frame (cons (cons 'name foobar) '((width . 80) (height . 24))))
于 2013-11-04T19:25:07.857 回答
4
(require 'list-utils)
(list-utils-flatten '((name . "Blarg") (width . 80) (height . 24)))
;; (name "Blarg" width 80 height 24)

list-utils可从 MELPA 安装。

或使用loop

(loop for (head . tail) in '((name . "Blarg") (width . 80) (height . 24))
      nconc (list head tail))
;; (name "Blarg" width 80 height 24)

(loop for (head . tail) on '(name "Blarg" width 80 height 24) by 'cddr
      collect (cons head (car tail)))
;; ((name . "Blarg") (width . 80) (height . 24))

这就是我的做法(需要cl库)

于 2013-11-04T18:42:17.533 回答
1

使用列表和树操作库,您可以通过将点对转换为 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)+4dash有这样的折叠称为-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)
于 2014-08-06T10:34:46.857 回答