简体   繁体   中英

How do I find and insert the average of multiple lines in Emacs / Elisp?

I have a file that looks similar to:

 AT 4
 AT 5.6
 AT 7.2
 EG  6
 EG  6
 S  2
 OP 3
 OP 1.2
 OP 40

and I want to compute the average (I've just made these averages up) for each of the titles and output something like:

AT 5.42
EG 6
S 2
OP 32.1

The file is in order, so all headings will be right under each other, but there are a varying amount of headings. eg. AT has three, but S only has one.

How would I sum together each of these lines, divide by the number of lines, and then replace all of the lines in emacs / elisp?

I decided to try to solve this question while still learning elisp myself. There is perhaps more efficient ways to solve this.

After defining the function, you'll want to set the region around the scores. (If the whole file, then M-<, C-SPC, M->) I figured this would be cleanest since your scores may be in the middle of other text. My function will compute the averages and then insert the answer at the end of the region.

(defun my/averages (beg end)
  (interactive "r")
  (let ((avgs (make-hash-table :test 'equal))
        (answer "")
        (curval nil)
        (key nil)
        (val nil))

    ; Process each line in region
    (save-excursion
      (goto-char beg)
      (while (< (point) end)

        ; split line
        (let ((split-line
               (split-string
                (buffer-substring-no-properties
                 (line-beginning-position) (line-end-position)))))
          (setq
           key (car split-line)
           val (string-to-number (cadr split-line))
           curval (gethash key avgs '(0 . 0)))
          (puthash key (cons (+ (car curval) 1) (+ (cdr curval) val )) avgs))

        ; Advance to next line
        (forward-line))

      ; Accumulate answer string
      (maphash
       (lambda (k v)
         (setq answer
               (concat answer "\n" k " "
                       (number-to-string (/ (cdr v) (car v))))))
       avgs)

      (end-of-line)
      (insert answer))))

As a warning, I have zero error checking for lines that do not strictly meet your formatting.

You need libraries dash , s , f , and their functions -map , -sum , -group-by , s-split , f-read-text .

;; average
(defun avg (values)
  (/ (-sum values) (length values)))

(-map (lambda (item)
        (list (car item)
              (avg (-map (lambda (x)
                           (string-to-number (cadr x)))
                         (cdr item)))))
      (-group-by (lambda (item)
                   (car item))
                 (-map (lambda (line)
                         (s-split " " line t))
                       (s-split "[\n\r]"
                                (f-read-text "file.txt")
                                t))))

Presuming your file is called "file.txt" , the code above returns (("AT" 5.6000000000000005) ("EG" 6) ("S" 2) ("OP" 14.733333333333334)) .

After that you can convert that into text:

(s-join "\n"
        (-map (lambda (item)
                (s-join " "
                        (list (car item)
                              (number-to-string (cadr item)))))

This string you can write into file using f-write-text . Don't forget you can format ugly floating-point numbers like that:

(format "%.2f" 3.33333333) ; => "3.33"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM