|
| 1 | +;;; parallel-letter-frequency.el --- Parallel Letter Frequency (exercism) -*- lexical-binding: t; -*- |
| 2 | + |
| 3 | +;;; Commentary: |
| 4 | + |
| 5 | +;;; Code: |
| 6 | + |
| 7 | +(require 'cl-lib) |
| 8 | + |
| 9 | + |
| 10 | +(defun clean-text (text) |
| 11 | + (downcase (replace-regexp-in-string "[^[:alpha:]]" "" text))) |
| 12 | + |
| 13 | + |
| 14 | +(defun combine-frequencies (freqs-list) |
| 15 | + (let ((combined-freqs (make-hash-table :test 'equal))) |
| 16 | + (dolist (freqs freqs-list) |
| 17 | + (maphash (lambda (key value) |
| 18 | + (puthash key (+ value (gethash key combined-freqs 0)) combined-freqs)) |
| 19 | + freqs)) |
| 20 | + combined-freqs)) |
| 21 | + |
| 22 | + |
| 23 | +(defun calculate-frequencies (texts) |
| 24 | + (let ((cleaned-texts (mapcar #'clean-text texts))) |
| 25 | + (if (cl-every #'string-empty-p cleaned-texts) |
| 26 | + (make-hash-table :test 'equal) |
| 27 | + (let* ((num-processes (min (length cleaned-texts) (max 1 (string-to-number (shell-command-to-string "nproc"))))) |
| 28 | + (texts-per-process (ceiling (/ (float (length cleaned-texts)) num-processes))) |
| 29 | + (results (make-hash-table :test 'equal)) |
| 30 | + (pending num-processes) |
| 31 | + (final-result (make-hash-table :test 'equal)) |
| 32 | + (processes nil)) |
| 33 | + (dotimes (i num-processes) |
| 34 | + (let* ((start-index (* i texts-per-process)) |
| 35 | + (end-index (min (* (1+ i) texts-per-process) (length cleaned-texts))) |
| 36 | + (process-texts (if (< start-index (length cleaned-texts)) |
| 37 | + (cl-subseq cleaned-texts start-index end-index) |
| 38 | + '()))) |
| 39 | + (when (not (null process-texts)) |
| 40 | + (let* ((command (prin1-to-string `(calculate-frequencies-in-subprocess ',process-texts))) |
| 41 | + (process (make-process |
| 42 | + :name (format "letter-freq-process-%d" i) |
| 43 | + :buffer (generate-new-buffer (format " *letter-freq-process-%d*" i)) |
| 44 | + :command (list "emacs" "--batch" "-l" "parallel-letter-frequency.el" "--eval" command) |
| 45 | + :sentinel (lambda (proc _event) |
| 46 | + (when (eq (process-status proc) 'exit) |
| 47 | + (with-current-buffer (process-buffer proc) |
| 48 | + (let ((result (read (buffer-string)))) |
| 49 | + (maphash (lambda (key value) |
| 50 | + (puthash key (+ value (gethash key results 0)) results)) |
| 51 | + result)) |
| 52 | + (setq pending (1- pending)) |
| 53 | + (when (= pending 0) |
| 54 | + (setq final-result (combine-frequencies (list results)))))))))) |
| 55 | + (push process processes))))) |
| 56 | + (while (> pending 0) |
| 57 | + (sleep-for 0.1)) |
| 58 | + final-result)))) |
| 59 | + |
| 60 | + |
| 61 | +(defun calculate-frequencies-in-subprocess (texts) |
| 62 | + (let ((freqs (make-hash-table :test 'equal))) |
| 63 | + (dolist (text texts) |
| 64 | + (let ((text-freqs (make-hash-table :test 'equal))) |
| 65 | + (dolist (char (string-to-list text)) |
| 66 | + (when (string-match-p "[[:alpha:]]" (char-to-string char)) |
| 67 | + (puthash |
| 68 | + char (1+ (gethash char text-freqs 0)) text-freqs))) |
| 69 | + (maphash |
| 70 | + (lambda (key value) |
| 71 | + (puthash key (+ value (gethash key freqs 0)) freqs)) |
| 72 | + text-freqs))) |
| 73 | + (prin1 freqs))) |
| 74 | + |
| 75 | + |
| 76 | +(provide 'parallel-letter-frequency) |
| 77 | +;;; parallel-letter-frequency.el ends here |
0 commit comments