Skip to content

Commit

Permalink
Parallel letter frequency (exercism#422)
Browse files Browse the repository at this point in the history
* 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 <459631+fapdash@users.noreply.github.com>
Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com>
3 people authored Jul 3, 2024
1 parent 8c5e45e commit e2646c7
Showing 8 changed files with 474 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -910,6 +910,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 5
},
{
"slug": "parallel-letter-frequency",
"name": "Parallel Letter Frequency",
"uuid": "3daf3903-1eb0-49b9-827a-76e6b7ca25fb",
"practices": [],
"prerequisites": [],
"difficulty": 10
}
]
},
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Instructions append

## Using Parallelism

The goal of this exercise is to practice parallelism with Emacs Lisp.

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)).

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 number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Instructions

Count the frequency of letters in texts using parallel computation.

Parallelism is about doing things in parallel that can also be done sequentially.
A common example is counting the frequency of letters.
Employ parallelism to calculate the total frequency of each letter in a list of texts.
21 changes: 21 additions & 0 deletions exercises/practice/parallel-letter-frequency/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"authors": [
"kmarker1101"
],
"contributors": [
"fapdash",
"BNAndras"
],
"files": {
"solution": [
"parallel-letter-frequency.el"
],
"test": [
"parallel-letter-frequency-test.el"
],
"example": [
".meta/example.el"
]
},
"blurb": "Count the frequency of letters in texts using parallel computation."
}
77 changes: 77 additions & 0 deletions exercises/practice/parallel-letter-frequency/.meta/example.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
;;; parallel-letter-frequency.el --- Parallel Letter Frequency (exercism) -*- lexical-binding: t; -*-

;;; Commentary:

;;; Code:

(require 'cl-lib)


(defun clean-text (text)
(downcase (replace-regexp-in-string "[^[:alpha:]]" "" text)))


(defun combine-frequencies (freqs-list)
(let ((combined-freqs (make-hash-table :test 'equal)))
(dolist (freqs freqs-list)
(maphash (lambda (key value)
(puthash key (+ value (gethash key combined-freqs 0)) combined-freqs))
freqs))
combined-freqs))


(defun calculate-frequencies (texts)
(let ((cleaned-texts (mapcar #'clean-text texts)))
(if (cl-every #'string-empty-p cleaned-texts)
(make-hash-table :test 'equal)
(let* ((num-processes (min (length cleaned-texts) (max 1 (string-to-number (shell-command-to-string "nproc")))))
(texts-per-process (ceiling (/ (float (length cleaned-texts)) num-processes)))
(results (make-hash-table :test 'equal))
(pending num-processes)
(final-result (make-hash-table :test 'equal))
(processes nil))
(dotimes (i num-processes)
(let* ((start-index (* i texts-per-process))
(end-index (min (* (1+ i) texts-per-process) (length cleaned-texts)))
(process-texts (if (< start-index (length cleaned-texts))
(cl-subseq cleaned-texts start-index end-index)
'())))
(when (not (null process-texts))
(let* ((command (prin1-to-string `(calculate-frequencies-in-subprocess ',process-texts)))
(process (make-process
:name (format "letter-freq-process-%d" i)
:buffer (generate-new-buffer (format " *letter-freq-process-%d*" i))
:command (list "emacs" "--batch" "-l" "parallel-letter-frequency.el" "--eval" command)
:sentinel (lambda (proc _event)
(when (eq (process-status proc) 'exit)
(with-current-buffer (process-buffer proc)
(let ((result (read (buffer-string))))
(maphash (lambda (key value)
(puthash key (+ value (gethash key results 0)) results))
result))
(setq pending (1- pending))
(when (= pending 0)
(setq final-result (combine-frequencies (list results))))))))))
(push process processes)))))
(while (> pending 0)
(sleep-for 0.1))
final-result))))


(defun calculate-frequencies-in-subprocess (texts)
(let ((freqs (make-hash-table :test 'equal)))
(dolist (text texts)
(let ((text-freqs (make-hash-table :test 'equal)))
(dolist (char (string-to-list text))
(when (string-match-p "[[:alpha:]]" (char-to-string char))
(puthash
char (1+ (gethash char text-freqs 0)) text-freqs)))
(maphash
(lambda (key value)
(puthash key (+ value (gethash key freqs 0)) freqs))
text-freqs)))
(prin1 freqs)))


(provide 'parallel-letter-frequency)
;;; parallel-letter-frequency.el ends here
49 changes: 49 additions & 0 deletions exercises/practice/parallel-letter-frequency/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[c054d642-c1fa-4234-8007-9339f2337886]
description = "no texts"

[818031be-49dc-4675-b2f9-c4047f638a2a]
description = "one text with one letter"

[c0b81d1b-940d-4cea-9f49-8445c69c17ae]
description = "one text with multiple letters"

[708ff1e0-f14a-43fd-adb5-e76750dcf108]
description = "two texts with one letter"

[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0]
description = "two texts with multiple letters"

[6366e2b8-b84c-4334-a047-03a00a656d63]
description = "ignore letter casing"

[92ebcbb0-9181-4421-a784-f6f5aa79f75b]
description = "ignore whitespace"

[bc5f4203-00ce-4acc-a5fa-f7b865376fd9]
description = "ignore punctuation"

[68032b8b-346b-4389-a380-e397618f6831]
description = "ignore numbers"

[aa9f97ac-3961-4af1-88e7-6efed1bfddfd]
description = "Unicode letters"

[7b1da046-701b-41fc-813e-dcfb5ee51813]
description = "combination of lower- and uppercase letters, punctuation and white space"

[4727f020-df62-4dcf-99b2-a6e58319cb4f]
description = "large texts"

[adf8e57b-8e54-4483-b6b8-8b32c115884c]
description = "many small texts"

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
;;; parallel-letter-frequency.el --- Parallel Letter Frequency (exercism) -*- lexical-binding: t; -*-

;;; Commentary:

;;; Code:


(defun calculate-frequencies (texts)
(error "Delete this S-Expression and write your own implementation"))


(provide 'parallel-letter-frequency)
;;; parallel-letter-frequency.el ends here

0 comments on commit e2646c7

Please sign in to comment.