elisp реализация команды uniq -c Unix для подсчета уникальных строк

Если есть данные в регионе:

flower
park
flower
stone
flower
stone
stone
flower

M-x some-command должен дать мне в другом буфере:

4 flower
2 stone
1 park 

Затем эти данные можно отсортировать по частоте или элементу.


person aartist    schedule 05.12.2017    source источник
comment
Количество совпадений подсчитывается для одного элемента. uniq -c дает количество для нескольких элементов в списке   -  person aartist    schedule 06.12.2017


Ответы (3)


Я полагаю, что распространенным методом было бы просто хешировать строки, а затем печатать содержимое. Этот подход может быть легко реализован в emacs.

;; See the emacs manual for creating a hash table test
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Hash.html
(defun case-fold-string= (a b)
  (eq t (compare-strings a nil nil b nil nil t)))
(defun case-fold-string-hash (a)
  (sxhash (upcase a)))

(define-hash-table-test 'case-fold
  'case-fold-string= 'case-fold-string-hash)

(defun uniq (beg end)
  "Print counts of strings in region."
  (interactive "r")
  (let ((h (make-hash-table :test 'case-fold))
        (lst (split-string (buffer-substring-no-properties beg end) "\n"
                           'omit-nulls " "))
        (output-func (if current-prefix-arg 'insert 'princ)))
    (dolist (str lst) 
      (puthash str (1+ (gethash str h 0)) h))
    (maphash (lambda (key val)
               (apply output-func (list (format "%d: %s\n" val key))))
             h)))

Вывод при выборе этого текста

4: flower
1: park
3: stone
person Rorschach    schedule 06.12.2017
comment
Красиво и быстро, это. Однако не уверены, что вам нужно такое поведение с опусканием нулей и обрезкой строк? - person phils; 07.12.2017
comment
Я предполагаю, что последовательность maphash не определена? - person phils; 07.12.2017
comment
@phils верно, если бы сортировка по ключу/значению была желательна, я бы добавил что-то вроде (push (cons val key) result) в функцию maphash, а затем (cl-sort results #'> :key #'car) - person Rorschach; 07.12.2017

Я полагаю, что есть много подходов, которые вы могли бы использовать для этого. Вот довольно простой подход:

(defun uniq-c (beginning end)
  "Like M-| uniq -c"
  (interactive "r")
  (let ((source (current-buffer))
        (dest (generate-new-buffer "*uniq-c*"))
        (case-fold-search nil))
    (set-buffer dest)
    (insert-buffer-substring source beginning end)
    (goto-char (point-min))
    (while (let* ((line (buffer-substring (line-beginning-position)
                                          (line-end-position)))
                  (pattern (concat "^" (regexp-quote line) "$"))
                  (count (count-matches pattern (point) (point-max))))
             (insert (format "%d " count))
             (forward-line 1)
             (flush-lines pattern)
             (not (eobp))))
    (pop-to-buffer dest)))
person phils    schedule 07.12.2017
comment
Не в последнюю очередь из-за того, что это полностью основано на регулярном выражении без какой-либо полезной причины, это не будет ужасно эффективным решением; но это должно быть легко читать/понимать. - person phils; 07.12.2017
comment
Кроме того, обратите внимание, что этот процесс имеет сложность O (n ^ 2) по сравнению с O (n) для подхода jenesaisquoi, основанного на хеше. - person phils; 07.12.2017

Это похоже на uniq -c в bash.

Тогда почему бы не использовать uniq -c?

С выделенным регионом M-| "sort | uniq -c" запустит эту команду в текущем регионе. Результаты отобразятся в минибуфере и будут перечислены в буфере *Messages*. Добавление префикса arg приведет к вставке результатов в текущий буфер.

person 0x5453    schedule 06.12.2017
comment
uniq -c изначально недоступен в некоторых средах. Вот и вся причина вопроса. - person aartist; 07.12.2017