Emacs 清点物品的优雅方式

Emacs 清点物品的优雅方式,emacs,lisp,elisp,Emacs,Lisp,Elisp,我有一个这样的列表: '(("Alpha" . 1538) ("Beta" . 8036) ("Gamma" . 8990) ("Beta" . 10052) ("Alpha" . 12837) ("Beta" . 13634) ("Beta" . 14977) ("Beta" . 15719) ("Alpha" . 17075) ("Rho" . 18949) ("Gamma"

我有一个这样的列表:

  '(("Alpha" .  1538)
    ("Beta"  .  8036)
    ("Gamma" .  8990)
    ("Beta"  .  10052)
    ("Alpha" .  12837)
    ("Beta"  .  13634)
    ("Beta"  .  14977)
    ("Beta"  .  15719)
    ("Alpha" .  17075)
    ("Rho"   .  18949)
    ("Gamma" .  21118)
    ("Gamma" .  26923)
    ("Alpha" .  31609))
(reduce '+ '(1 2 3 4 5)) ; 15
(reduce '* '(1 2 3 4 5)) ; 120
(reduce 'min '(1 2 3 4 5)) ; 1
如何计算列表中每个元素在car中出现的术语总数?基本上我想要:

(("Alpha" . 4)
 ("Beta" . 5)
 ("Gamma" . 3)
 ("Rho" . 1))
不,这不是家庭作业。我只是还没有“用口齿不清的语言思考”的东西

在C#中,我将使用LINQ来实现这一点。我也可以用lisp语言,使用while循环等等,但是我想用的方式似乎太复杂了


编辑

这就是我所拥有的:

(defun count-uniq (list)
  "Returns an alist, each item is a cons cell where the car is
a unique element of LIST, and the cdr is the number of occurrences of that
unique element in the list. "
  (flet ((helper (list new)
                 (if (null list)
                     new
                   (let ((elt (assoc (car list) new)))
                     (helper (cdr list)
                             (if elt
                                 (progn (incf (cdr elt)) new)
                               (cons (cons (car list) 1) new)))))))
    (nreverse (helper list nil))))

我不知道这是最优雅的,但似乎是合理的:

(defun add-for-cheeso (data)
  (let (result)
    (dolist (elt data result)
      (let ((sofar (assoc (car elt) result)))
        (if sofar
            (setcdr sofar (1+ (cdr sofar)))
          (push (cons (car elt) 1) result))))))
(除频(列表和可选测试键)
(let((h(生成哈希表:test)))
(dolist(x列表)
(let((键(如果键(funcall键x)x)))
(puthash键(1+(gethash键h0))h)
(让((r无))
(mapphash#’(lambda(k v)(push(cons k v)r))h)
(排序r#’(lambda(x y)(<(cdr x)(cdr y()())))
(freqs'(“Alpha”.1538)
(“Beta.8036)
(“Gamma”.8990)
(“Beta.10052)
(“Alpha.12837)
(“Beta.13634)
(“Beta.14977)
(“Beta.15719)
(“Alpha.17075)
(“Rho.18949)
(“伽马”21118)
(“伽马”26923)
(“Alpha.31609))
#“相等”车)

组合更高级别的通用Lisp函数:

(defun count-unique (alist) 
  (mapcar
    (lambda (item)
      (cons (car item)
            (count (car item) alist :test #'equal :key #'car)))
    (remove-duplicates alist :test #'equal :key #'car)))
但它不能扩展到大型列表。如果需要O(n)性能,请使用基于哈希表的解决方案,例如不太优雅的:

(defun count-unique (alist)
  (loop
     with hash = (make-hash-table :test #'equal)
     for (key . nil) in alist
     do (incf (gethash key hash 0))
     finally (return
               (loop for key being each hash-key of hash
                  using (hash-value value)
                  collect (cons key value)))))

使用通用Lisp扩展:

(require 'cl)
(loop with result = nil
      for (key . dummy) in original-list
      do (incf (cdr (or (assoc key result)
                        (first (push (cons key 0) result)))))
      finally return (sort result
                           (lambda (a b) (string< (car a) (car b)))))
(需要“cl”)
(结果为零的循环)
对于原始列表中的(key.dummy)
do(incf(cdr)(或(关联关键结果)
(第一个(按下(控制键0)结果(()())))
最后返回(排序结果)
(lambda(ab)(字符串<(a车)(b车(())))
如果您不关心最终结果的排序,您可以说
最终返回结果。

(需要'cl')
(require 'cl)
(defun count-uniq (list)
  (let ((k 1) (list (sort (mapcar #'car list) #'string<)))
    (loop for (i . j) on list
          when (string= i (car j)) do (incf k)
          else collect (cons i k) and do (setf k 1))))
(定义计数单位(列表)
(let((k1)(list)(sort(mapcar#'car list)#'string使用高阶函数进行排序和约简


第一个排序(使用string我认为这是一个优雅的功能解决方案,使用Emacs的alist函数,产生一个可重用的
frequencies
函数,类似于Eli的答案:

(defun frequencies (vals)
  (reduce
   (lambda (freqs key)
     (cons (cons key (+ 1 (or (cdr (assoc key freqs)) 0)))
           (assq-delete-all-with-test key freqs 'equal)))
   vals
   :initial-value nil)))

(frequencies (mapcar 'car
                     '(("Alpha" .  1538)
                       ("Beta"  .  8036)
                       ("Gamma" .  8990)
                       ("Beta"  .  10052)
                       ("Alpha" .  12837)
                       ("Beta"  .  13634)
                       ("Beta"  .  14977)
                       ("Beta"  .  15719)
                       ("Alpha" .  17075)
                       ("Rho"   .  18949)
                       ("Gamma" .  21118)
                       ("Gamma" .  26923)
                       ("Alpha" .  31609))))
=> (("Alpha" . 4) ("Gamma" . 3) ("Rho" . 1) ("Beta" . 5))

每当您想要遍历一个列表并在之后返回一些值时,无论是一个新列表还是一些聚合结果,您都会想到一个,也称为“reduce”在Python和Lisps中。Fold是一个很好的抽象,因为它允许编写通用代码,只需调整一些元素即可适用于许多用例。查找几个数字的和、查找一个乘积、查找一个最小整数之间有什么相似之处?它们都是Fold,因为您遍历了列表,然后根据其co返回一些结果在Emacs Lisp中,它们看起来如下:

  '(("Alpha" .  1538)
    ("Beta"  .  8036)
    ("Gamma" .  8990)
    ("Beta"  .  10052)
    ("Alpha" .  12837)
    ("Beta"  .  13634)
    ("Beta"  .  14977)
    ("Beta"  .  15719)
    ("Alpha" .  17075)
    ("Rho"   .  18949)
    ("Gamma" .  21118)
    ("Gamma" .  26923)
    ("Alpha" .  31609))
(reduce '+ '(1 2 3 4 5)) ; 15
(reduce '* '(1 2 3 4 5)) ; 120
(reduce 'min '(1 2 3 4 5)) ; 1
但是折叠比这更为普遍。查找和、计算列表中的偶数、删除每个奇数和构建一个每增加5个数的列表之间有什么相似之处?每个这样的函数都可以通过取一些基值,依次变换它,直到得到结果。取这个基价值,比喻一团粘土,称之为“累加器”,然后从列表中选取一个元素,根据这个元素对这团粘土做一些处理,使其成为一个宏伟雕塑的草图。然后从列表中选取下一个元素,对你的雕塑做一些新的处理。你重复这个步骤,直到列表为空,你最终得到一件杰作。就好像列表中的每个元素都是一个单独的元素大配方中的说明。请记住,您完全可以自由地使用粘土进行任何操作,您不必直接在技术上使用结果中的列表元素,这意味着累加器(以及结果)可能不同

关于从末尾减少的注意事项:Lisps中的列表不像Python或Java中的智能数组,它们是链表,因此访问或更改列表中某个位置的元素是一个O(n)操作,而“考虑”列表的开头是O(1)。换句话说,在列表的末尾追加一个元素是非常昂贵的,因此Lisper通常会在列表的开头添加元素,然后最终反转列表,这就是调用的。如果我们在最后两个函数中执行了普通的reduce,我们将cons 1加至累加器并获取(1),然后cons 2加至累加器并获取(2 1),直到得到正确的结果,但结果是颠倒的。我们可以在之后使用
reverse
函数,但幸运的是,Emacs的
reduce
支持
:from end
关键字参数,因此它需要5、4、3,依此类推

现在很清楚,您的操作是一个折叠,遍历原始的alist并计算每个键的出现次数。在编写折叠之前,让我们先谈谈alists。Lisp中的alist是一个穷人的哈希表。您通常不会修改编程语言的哈希表实现细节,是吗?您使用的是API。在Python中,这个API看起来像方括号语法(
d['a']=1
)和dict方法(
d.keys()
)。对于alists,API包含函数
assoc
,该函数返回一个由键提供的项

(assoc 'a '((a . 1) (b . 2))) ; (a . 1)
为什么我要谈论实现细节?因为你通过
assoc
工作,你不在乎这个列表到底是什么样子,你把它抽象掉。另一个API是,如果你想添加一个新元素或更改一个现有元素,你只需在列表上加一个点对。这就是你应该如何处理这个列表,regardle例如,如果我想将键
a
的值更改为10,我只需运行
(cons'(a.10)我的列表)
,而
我的列表
最终将成为
((a.10)(a.1)(b.2))
。但这没有问题,因为
assoc
只返回第一个点对,而忽略其余的点对,所以
(assoc 'a '((a . 1) (b . 2))) ; (a . 1)
(reduce (lambda (acc x)
          (let* ((key (car x))
                 (pair (assoc key acc))
                 (count (cdr pair)))
            (if pair
                (cons (cons key (1+ count)) acc)
              (cons (cons key 1) acc))))
        my-alist
        :initial-value '())
(("Alpha" . 4) ("Gamma" . 3) ("Gamma" . 2) ("Rho" . 1) ("Alpha" . 3)
 ("Beta" . 5) ("Beta" . 4) ("Beta" . 3) ("Alpha" . 2) ("Beta" . 2)
 ("Gamma" . 1) ("Beta" . 1) ("Alpha" . 1))
(reduce (lambda (acc x)
          (cl-incf (gethash (car x) acc 0))
          acc)
        my-alist
        :initial-value (make-hash-table :test 'equal))
(defun ht->alist (table)
  (let (alist)
    (maphash (lambda (k v)
               (push (cons k v) alist))
             table)
    alist))
(require 'dash)

(defun frequencies (values)
  "Return an alist indicating the frequency of values in VALUES list."
  (mapcar (-lambda ((value . items))
          (cons value (length items)))
        (-group-by #'identity
                   values)))

(frequencies (mapcar #'car my-list))