diff --git a/config.json b/config.json index 8506065253..50a9943f6e 100644 --- a/config.json +++ b/config.json @@ -54,6 +54,20 @@ "logic" ] }, + { + "uuid": "7126f86c-0e7f-7080-b4cb-3c8457dfcadcfe7e446", + "slug": "parallel-letter-frequency", + "core": false, + "unlocked_by": null, + "difficulty": 5, + "topics": [ + "threading", + "parallellism", + "loops", + "queues", + "strings" + ] + }, { "uuid": "ca7c5ef1-5135-4fb4-8e68-669ef0f2a51a", "slug": "rna-transcription", diff --git a/exercises/parallel-letter-frequency/README.md b/exercises/parallel-letter-frequency/README.md new file mode 100644 index 0000000000..41da82774c --- /dev/null +++ b/exercises/parallel-letter-frequency/README.md @@ -0,0 +1,24 @@ +# Parallel Letter Frequency + +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. +Create a function that returns the total frequency of each letter in a +list of texts and that employs parallelism. + +The letters used consists of ASCII letters `a` to `z`, inclusive, and is case +insensitive. + +## Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/` directory. + +For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit /python/bob/bob.py`. + + +For more detailed information about running tests, code style and linting, +please see the [help page](http://exercism.io/languages/python). + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/parallel-letter-frequency/example.py b/exercises/parallel-letter-frequency/example.py new file mode 100644 index 0000000000..89a475d568 --- /dev/null +++ b/exercises/parallel-letter-frequency/example.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from collections import Counter +from threading import Lock, Thread +from time import sleep +import sys +if sys.version[0] == '2': + from Queue import Queue +else: + from queue import Queue + +total_workers = 3 # Maximum number of threads chosen arbitrarily + + +class LetterCounter(object): + + def __init__(self): + self.lock = Lock() + self.value = Counter() + + def add_counter(self, counter_to_add): + self.lock.acquire() + try: + self.value = self.value + counter_to_add + finally: + self.lock.release() + + +def count_letters(queue_of_texts, letter_to_frequency, worker_id): + while not queue_of_texts.empty(): + sleep(worker_id + 1) + line_input = queue_of_texts.get() + if line_input is not None: + letters_in_line = Counter([x for x in line_input.lower() if + x.isalpha()]) + letter_to_frequency.add_counter(letters_in_line) + queue_of_texts.task_done() + if line_input is None: + break + + +def calculate(list_of_texts): + queue_of_texts = Queue() + [queue_of_texts.put(line) for line in list_of_texts] + letter_to_frequency = LetterCounter() + threads = [] + for i in range(total_workers): + worker = Thread(target=count_letters, args=(queue_of_texts, + letter_to_frequency, i)) + worker.start() + threads.append(worker) + queue_of_texts.join() + for i in range(total_workers): + queue_of_texts.put(None) + for t in threads: + t.join() + return letter_to_frequency.value diff --git a/exercises/parallel-letter-frequency/parallel_letter_frequency.py b/exercises/parallel-letter-frequency/parallel_letter_frequency.py new file mode 100644 index 0000000000..1b9e1f7f9c --- /dev/null +++ b/exercises/parallel-letter-frequency/parallel_letter_frequency.py @@ -0,0 +1,2 @@ +def calculate(text_input): + pass diff --git a/exercises/parallel-letter-frequency/parallel_letter_frequency_test.py b/exercises/parallel-letter-frequency/parallel_letter_frequency_test.py new file mode 100644 index 0000000000..462d2fe717 --- /dev/null +++ b/exercises/parallel-letter-frequency/parallel_letter_frequency_test.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from collections import Counter +import unittest + +from parallel_letter_frequency import calculate + + +class ParallelLetterFrequencyTest(unittest.TestCase): + def test_one_letter(self): + actual = calculate(['a']) + expected = {'a': 1} + self.assertDictEqual(actual, expected) + + def test_case_insensitivity(self): + actual = calculate(['aA']) + expected = {'a': 2} + self.assertDictEqual(actual, expected) + + def test_numbers(self): + actual = calculate(['012', '345', '6789']) + expected = {} + self.assertDictEqual(actual, expected) + + def test_punctuations(self): + actual = calculate(['[]\;,', './{}|', ':"<>?']) + expected = {} + self.assertDictEqual(actual, expected) + + def test_whitespaces(self): + actual = calculate([' ', '\t ', '\n\n']) + expected = {} + self.assertDictEqual(actual, expected) + + def test_repeated_string_with_known_frequencies(self): + letter_frequency = 3 + text_input = 'abc\n' * letter_frequency + actual = calculate(text_input.split('\n')) + expected = {'a': letter_frequency, 'b': letter_frequency, + 'c': letter_frequency} + self.assertDictEqual(actual, expected) + + def test_multiline_text(self): + text_input = "3 Quotes from Excerism Homepage:\n" + \ + "\tOne moment you feel like you're\n" + \ + "getting it. The next moment you're\n" + \ + "stuck.\n" + \ + "\tYou know what it’s like to be fluent.\n" + \ + "Suddenly you’re feeling incompetent\n" + \ + "and clumsy.\n" + \ + "\tHaphazard, convoluted code is\n" + \ + "infuriating, not to mention costly. That\n" + \ + "slapdash explosion of complexity is an\n" + \ + "expensive yak shave waiting to\n" + \ + "happen." + actual = calculate(text_input.split('\n')) + expected = Counter([x for x in text_input.lower() if x.isalpha()]) + self.assertDictEqual(actual, expected) + + +if __name__ == '__main__': + unittest.main()