按索引对表或列表进行分组

时间:2017-04-11 00:02:46

标签: list emacs org-table

如何在elisp中按给定索引对列表进行分组?列表表示表,如组织表,因此每个子列表代表一行,例如

| a | 1 | 0 |
| b | 1 | 1 |
| c | 0 | 0 |

将是'((a 1 0) (b 1 1) (c 0 0))

我希望能够将另一列的给定列分组。因此,例如,将第一列分组为第三列,我希望'((0 a c) (1 b)),因为第一行和第三行的第三列为0。

我已经尝试了以下代码,但它创建了很多循环。是否有elisp中的分组功能或更好的方式?

;; group column1 by column2 in table
(defun group-by (col1 col2 table)
  (let ((vals (cl-remove-duplicates     ;find unique values to group by
               (cl-loop for row in table
                  collect (nth col2 row)))))
    (cl-loop for val in vals            ;for each unique value
       collect (cons val (cl-loop for row in table ;check each row for match
                            when (eq val (nth col2 row))
                            collect (nth col1 row))))))

(defvar tst-data '((a 1 0) (b 1 1) (c 0 0)))
(group-by 0 2 tst-data)
;; ((1 b)
;;  (0 a c))

1 个答案:

答案 0 :(得分:1)

ElispCookbook上恰好有一个函数可以对列表中的元素进行分组,称为group-by-eq。该函数依次使用一个函数f,该函数将应用于列表的每个元素(“行”),然后按该值对列表(“行”)进行分组。

使用该函数,我们可以通过传递一个调用nth的函数来按列函数编写一个组:

(defun group-by-col (n table)
  (group-by-eq (lambda (row) (nth n row)) table))

你的问题要求进行双重分组,但让我们只进行一次分组:

(let ((test-data '((a 1 0)
                   (b 1 1)
                   (c 0 0))))
  (mapcar 'cdr (group-by-col 2 test-data)))
;; (((c 0 0) (a 1 0)) ((b 1 1)))

嗯,我认为有第二个分组,但是看起来你只想选择每个组的第n个元素,在这个例子中是第一个元素:

(let ((test-data '((a 1 0)
                   (b 1 1)
                   (c 0 0))))
  (mapcar
   (lambda (grp)
     (cons (car grp) (mapcar (lambda (lst) (nth 0 lst)) (cdr grp))))
   (group-by-col 2 test-data)))
;; ((0 c a) (1 b))