Skip to content

Commit 60ce289

Browse files
forgeRWsmalley
authored andcommitted
parallel-letter-frequency: Implement exercise to resolve exercism#744 (exercism#891)
* parallel-letter-frequency: Implement exercise to resolve exercism#744 * parallel-letter-frequency: Removed infinite loop * parallel-letter-frequency: README format fixes
1 parent b0a86c6 commit 60ce289

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed

config.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@
5454
"logic"
5555
]
5656
},
57+
{
58+
"uuid": "7126f86c-0e7f-7080-b4cb-3c8457dfcadcfe7e446",
59+
"slug": "parallel-letter-frequency",
60+
"core": false,
61+
"unlocked_by": null,
62+
"difficulty": 5,
63+
"topics": [
64+
"threading",
65+
"parallellism",
66+
"loops",
67+
"queues",
68+
"strings"
69+
]
70+
},
5771
{
5872
"uuid": "ca7c5ef1-5135-4fb4-8e68-669ef0f2a51a",
5973
"slug": "rna-transcription",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Parallel Letter Frequency
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
6+
sequentially. A common example is counting the frequency of letters.
7+
Create a function that returns the total frequency of each letter in a
8+
list of texts and that employs parallelism.
9+
10+
The letters used consists of ASCII letters `a` to `z`, inclusive, and is case
11+
insensitive.
12+
13+
## Submitting Exercises
14+
15+
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
16+
17+
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
18+
19+
20+
For more detailed information about running tests, code style and linting,
21+
please see the [help page](http://exercism.io/languages/python).
22+
23+
## Submitting Incomplete Solutions
24+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# -*- coding: utf-8 -*-
2+
from collections import Counter
3+
from threading import Lock, Thread
4+
from time import sleep
5+
import sys
6+
if sys.version[0] == '2':
7+
from Queue import Queue
8+
else:
9+
from queue import Queue
10+
11+
total_workers = 3 # Maximum number of threads chosen arbitrarily
12+
13+
14+
class LetterCounter(object):
15+
16+
def __init__(self):
17+
self.lock = Lock()
18+
self.value = Counter()
19+
20+
def add_counter(self, counter_to_add):
21+
self.lock.acquire()
22+
try:
23+
self.value = self.value + counter_to_add
24+
finally:
25+
self.lock.release()
26+
27+
28+
def count_letters(queue_of_texts, letter_to_frequency, worker_id):
29+
while not queue_of_texts.empty():
30+
sleep(worker_id + 1)
31+
line_input = queue_of_texts.get()
32+
if line_input is not None:
33+
letters_in_line = Counter([x for x in line_input.lower() if
34+
x.isalpha()])
35+
letter_to_frequency.add_counter(letters_in_line)
36+
queue_of_texts.task_done()
37+
if line_input is None:
38+
break
39+
40+
41+
def calculate(list_of_texts):
42+
queue_of_texts = Queue()
43+
[queue_of_texts.put(line) for line in list_of_texts]
44+
letter_to_frequency = LetterCounter()
45+
threads = []
46+
for i in range(total_workers):
47+
worker = Thread(target=count_letters, args=(queue_of_texts,
48+
letter_to_frequency, i))
49+
worker.start()
50+
threads.append(worker)
51+
queue_of_texts.join()
52+
for i in range(total_workers):
53+
queue_of_texts.put(None)
54+
for t in threads:
55+
t.join()
56+
return letter_to_frequency.value
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def calculate(text_input):
2+
pass
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# -*- coding: utf-8 -*-
2+
from collections import Counter
3+
import unittest
4+
5+
from parallel_letter_frequency import calculate
6+
7+
8+
class ParallelLetterFrequencyTest(unittest.TestCase):
9+
def test_one_letter(self):
10+
actual = calculate(['a'])
11+
expected = {'a': 1}
12+
self.assertDictEqual(actual, expected)
13+
14+
def test_case_insensitivity(self):
15+
actual = calculate(['aA'])
16+
expected = {'a': 2}
17+
self.assertDictEqual(actual, expected)
18+
19+
def test_numbers(self):
20+
actual = calculate(['012', '345', '6789'])
21+
expected = {}
22+
self.assertDictEqual(actual, expected)
23+
24+
def test_punctuations(self):
25+
actual = calculate(['[]\;,', './{}|', ':"<>?'])
26+
expected = {}
27+
self.assertDictEqual(actual, expected)
28+
29+
def test_whitespaces(self):
30+
actual = calculate([' ', '\t ', '\n\n'])
31+
expected = {}
32+
self.assertDictEqual(actual, expected)
33+
34+
def test_repeated_string_with_known_frequencies(self):
35+
letter_frequency = 3
36+
text_input = 'abc\n' * letter_frequency
37+
actual = calculate(text_input.split('\n'))
38+
expected = {'a': letter_frequency, 'b': letter_frequency,
39+
'c': letter_frequency}
40+
self.assertDictEqual(actual, expected)
41+
42+
def test_multiline_text(self):
43+
text_input = "3 Quotes from Excerism Homepage:\n" + \
44+
"\tOne moment you feel like you're\n" + \
45+
"getting it. The next moment you're\n" + \
46+
"stuck.\n" + \
47+
"\tYou know what it’s like to be fluent.\n" + \
48+
"Suddenly you’re feeling incompetent\n" + \
49+
"and clumsy.\n" + \
50+
"\tHaphazard, convoluted code is\n" + \
51+
"infuriating, not to mention costly. That\n" + \
52+
"slapdash explosion of complexity is an\n" + \
53+
"expensive yak shave waiting to\n" + \
54+
"happen."
55+
actual = calculate(text_input.split('\n'))
56+
expected = Counter([x for x in text_input.lower() if x.isalpha()])
57+
self.assertDictEqual(actual, expected)
58+
59+
60+
if __name__ == '__main__':
61+
unittest.main()

0 commit comments

Comments
 (0)