Skip to content

Commit e2646c7

Browse files
kmarker1101fapdashBNAndras
authored
Parallel letter frequency (exercism#422)
* add parallel-letter-frequency, part of 48 in 24 * practice-exercise-generator: Don't include commas in test names (exercism#420) fixes exercism#419 * fix merge conflict * fix json formating issue * fix merge issue * refactor to use parallism * check if ci failure was a fluke * fix ci failure * add empty string check * return hash-table, update difficulty * cleanup, fix error * add instructions.append * Use extra file for subprocess code * bin/test-examples: Handle testing of exercises with additional files * Don't use additional solution file * add BNAndras and fapdash as contributors * Use printed representation for hash table (de)serialization * Revert changes to `bin/test-examples` See exercism#422 (comment) * refactor tests * Typo fix --------- Co-authored-by: FAP <[email protected]> Co-authored-by: András B Nagy <[email protected]>
1 parent 8c5e45e commit e2646c7

File tree

8 files changed

+474
-0
lines changed

8 files changed

+474
-0
lines changed

config.json

+8
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,14 @@
910910
"practices": [],
911911
"prerequisites": [],
912912
"difficulty": 5
913+
},
914+
{
915+
"slug": "parallel-letter-frequency",
916+
"name": "Parallel Letter Frequency",
917+
"uuid": "3daf3903-1eb0-49b9-827a-76e6b7ca25fb",
918+
"practices": [],
919+
"prerequisites": [],
920+
"difficulty": 10
913921
}
914922
]
915923
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Instructions append
2+
3+
## Using Parallelism
4+
5+
The goal of this exercise is to practice parallelism with Emacs Lisp.
6+
7+
In Emacs Lisp this can be achieved by using [`asynchronous processes`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Asynchronous-Processes.html#:~:text=An%20asynchronous%20process%20is%20controlled,%2Dtype%20(see%20below)).
8+
9+
You may also want to look at the documentation for [`batch mode`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Batch-Mode.html), [`sentinels`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Sentinels.html) and [`receiving output from processes`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Output-from-Processes.html).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Instructions
2+
3+
Count the frequency of letters in texts using parallel computation.
4+
5+
Parallelism is about doing things in parallel that can also be done sequentially.
6+
A common example is counting the frequency of letters.
7+
Employ parallelism to calculate the total frequency of each letter in a list of texts.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"authors": [
3+
"kmarker1101"
4+
],
5+
"contributors": [
6+
"fapdash",
7+
"BNAndras"
8+
],
9+
"files": {
10+
"solution": [
11+
"parallel-letter-frequency.el"
12+
],
13+
"test": [
14+
"parallel-letter-frequency-test.el"
15+
],
16+
"example": [
17+
".meta/example.el"
18+
]
19+
},
20+
"blurb": "Count the frequency of letters in texts using parallel computation."
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[c054d642-c1fa-4234-8007-9339f2337886]
13+
description = "no texts"
14+
15+
[818031be-49dc-4675-b2f9-c4047f638a2a]
16+
description = "one text with one letter"
17+
18+
[c0b81d1b-940d-4cea-9f49-8445c69c17ae]
19+
description = "one text with multiple letters"
20+
21+
[708ff1e0-f14a-43fd-adb5-e76750dcf108]
22+
description = "two texts with one letter"
23+
24+
[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0]
25+
description = "two texts with multiple letters"
26+
27+
[6366e2b8-b84c-4334-a047-03a00a656d63]
28+
description = "ignore letter casing"
29+
30+
[92ebcbb0-9181-4421-a784-f6f5aa79f75b]
31+
description = "ignore whitespace"
32+
33+
[bc5f4203-00ce-4acc-a5fa-f7b865376fd9]
34+
description = "ignore punctuation"
35+
36+
[68032b8b-346b-4389-a380-e397618f6831]
37+
description = "ignore numbers"
38+
39+
[aa9f97ac-3961-4af1-88e7-6efed1bfddfd]
40+
description = "Unicode letters"
41+
42+
[7b1da046-701b-41fc-813e-dcfb5ee51813]
43+
description = "combination of lower- and uppercase letters, punctuation and white space"
44+
45+
[4727f020-df62-4dcf-99b2-a6e58319cb4f]
46+
description = "large texts"
47+
48+
[adf8e57b-8e54-4483-b6b8-8b32c115884c]
49+
description = "many small texts"

0 commit comments

Comments
 (0)