From 7aa57f5cee65fca7b05b647e5ed54960af517e5d Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Tue, 10 Oct 2017 06:41:27 +0800 Subject: [PATCH 01/49] Add parameters to exercise placeholder Add parameters to exercise placeholder for variable-length-quantity. Also changed variable naming from bytes to bytes_ to avoid shadowing python's bytes Fixes: #651 --- exercises/variable-length-quantity/example.py | 12 ++++++------ .../variable_length_quantity.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exercises/variable-length-quantity/example.py b/exercises/variable-length-quantity/example.py index a1d4bb6fa8..5a59cb7f24 100644 --- a/exercises/variable-length-quantity/example.py +++ b/exercises/variable-length-quantity/example.py @@ -3,32 +3,32 @@ def encode_single(n): - bytes = [n & SEVENBITSMASK] + bytes_ = [n & SEVENBITSMASK] n >>= 7 while n > 0: - bytes.append(n & SEVENBITSMASK | EIGHTBITMASK) + bytes_.append(n & SEVENBITSMASK | EIGHTBITMASK) n >>= 7 - return bytes[::-1] + return bytes_[::-1] def encode(numbers): return sum((encode_single(n) for n in numbers), []) -def decode(bytes): +def decode(bytes_): values = [] n = 0 - for i, byte in enumerate(bytes): + for i, byte in enumerate(bytes_): n <<= 7 n += (byte & SEVENBITSMASK) if byte & EIGHTBITMASK == 0: values.append(n) n = 0 - elif i == len(bytes) - 1: + elif i == len(bytes_) - 1: raise ValueError('incomplete byte sequence') return values diff --git a/exercises/variable-length-quantity/variable_length_quantity.py b/exercises/variable-length-quantity/variable_length_quantity.py index 2536c2e670..41bdcdc849 100644 --- a/exercises/variable-length-quantity/variable_length_quantity.py +++ b/exercises/variable-length-quantity/variable_length_quantity.py @@ -1,6 +1,6 @@ -def encode(): +def encode(numbers): pass -def decode(): +def decode(bytes_): pass From f9765ef19034d39ed0bf8e49656be9ebc003273d Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 10:55:31 -0500 Subject: [PATCH 02/49] (WIP) error-handling: implement exercise From 04f385b78a076220ac2cd2bb130e6b7682c9a3e8 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 11:02:34 -0500 Subject: [PATCH 03/49] error-handling: update config.json --- config.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config.json b/config.json index 83c94e441e..348ae504d6 100644 --- a/config.json +++ b/config.json @@ -854,6 +854,16 @@ "conditionals" ] }, + { + "uuid": "3a2a947a-01b3-1e80-e32b-de1756fd88365adf12e", + "slug": "error-handling", + "core": false, + "unlocked_by": null, + "difficulty": 3, + "topics": [ + "exception handling" + ] + }, { "uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd", "slug": "accumulate", From b1c2967cee8306d94b106fb6f67bdb6c44d6464f Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 11:03:44 -0500 Subject: [PATCH 04/49] error-handling: add README --- exercises/error-handling/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 exercises/error-handling/README.md diff --git a/exercises/error-handling/README.md b/exercises/error-handling/README.md new file mode 100644 index 0000000000..9039642454 --- /dev/null +++ b/exercises/error-handling/README.md @@ -0,0 +1,27 @@ +# Error Handling + +Implement various kinds of error handling and resource management. + +An important point of programming is how to handle errors and close +resources even if errors occur. + +This exercise requires you to handle various errors. Because error handling +is rather programming language specific you'll have to refer to the tests +for your track to see what's exactly required. + +### 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). + +## Source + +Problem 6 at Project Euler [http://projecteuler.net/problem=6](http://projecteuler.net/problem=6) + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. From 135cdb7f16372978774acf06d4da556d0a7a7db7 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 11:18:33 -0500 Subject: [PATCH 05/49] error-handling: add solution template --- exercises/error-handling/error_handling.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 exercises/error-handling/error_handling.py diff --git a/exercises/error-handling/error_handling.py b/exercises/error-handling/error_handling.py new file mode 100644 index 0000000000..f34ccee1cf --- /dev/null +++ b/exercises/error-handling/error_handling.py @@ -0,0 +1,14 @@ +def handle_error_by_throwing_exception(): + pass + + +def handle_error_by_returning_none(input_data): + pass + + +def handle_error_by_returning_tuple(input_data): + pass + + +def filelike_objects_are_closed_on_exception(filelike_object): + pass From e8e0ae1968e82468c7d590c37bd5b2ce0b767057 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 11:18:51 -0500 Subject: [PATCH 06/49] error-handling: write test cases --- .../error-handling/error_handling_test.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 exercises/error-handling/error_handling_test.py diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py new file mode 100644 index 0000000000..1436b2f35f --- /dev/null +++ b/exercises/error-handling/error_handling_test.py @@ -0,0 +1,46 @@ +import unittest + +import error_handling as er + + +class FileLike(object): + def __init__(self): + self.is_open = False + + def open(self): + self.is_open = True + + def close(self): + self.is_open = False + + +class ErrorHandlingTest(unittest.TestCase): + def test_throw_exception(self): + with self.assertRaises(Exception): + er.handle_error_by_throwing_exception() + + def test_return_none(self): + self.assertEqual(1, er.handle_error_by_returning_none('1'), + 'Result of valid input should not be None') + self.assertNone(er.handle_error_by_returning_none('a'), + 'Result of invalid input should be None') + + def test_return_tuple(self): + successful_result, result = er.handle_error_by_returning_tuple('1') + self.assertTrue(successful_result, 'Valid input should be successful') + self.assertEqual(1, result, 'Result of valid input should not be None') + + failure_result, result = er.handle_error_by_returning_tuple('a') + self.assertFalse(failure_result, + 'Invalid input should not be successful') + + def test_filelike_objects_are_closed_on_exception(self): + filelike_object = FileLike() + filelike_object.open() + with self.assertRaises(BaseException): + er.filelike_objects_are_closed_on_exception(filelike_object) + self.assertFalse(filelike_object.is_open) + + +if __name__ == '__main__': + unittest.main() From 2a75c7949da2cc34189140c4829f4b06d70b42b3 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 11:31:52 -0500 Subject: [PATCH 07/49] write example solution --- .../error-handling/error_handling_test.py | 10 +++++++-- exercises/error-handling/example.py | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 exercises/error-handling/example.py diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py index 1436b2f35f..0103431531 100644 --- a/exercises/error-handling/error_handling_test.py +++ b/exercises/error-handling/error_handling_test.py @@ -10,9 +10,15 @@ def __init__(self): def open(self): self.is_open = True + def __enter__(self): + self.is_open = True + def close(self): self.is_open = False + def __exit__(self): + self.is_open = False + class ErrorHandlingTest(unittest.TestCase): def test_throw_exception(self): @@ -22,7 +28,7 @@ def test_throw_exception(self): def test_return_none(self): self.assertEqual(1, er.handle_error_by_returning_none('1'), 'Result of valid input should not be None') - self.assertNone(er.handle_error_by_returning_none('a'), + self.assertIsNone(er.handle_error_by_returning_none('a'), 'Result of invalid input should be None') def test_return_tuple(self): @@ -37,7 +43,7 @@ def test_return_tuple(self): def test_filelike_objects_are_closed_on_exception(self): filelike_object = FileLike() filelike_object.open() - with self.assertRaises(BaseException): + with self.assertRaises(Exception): er.filelike_objects_are_closed_on_exception(filelike_object) self.assertFalse(filelike_object.is_open) diff --git a/exercises/error-handling/example.py b/exercises/error-handling/example.py new file mode 100644 index 0000000000..837f678380 --- /dev/null +++ b/exercises/error-handling/example.py @@ -0,0 +1,22 @@ +def handle_error_by_throwing_exception(): + raise Exception() + + +def handle_error_by_returning_none(input_data): + try: + return int(input_data) + except ValueError: + return None + + +def handle_error_by_returning_tuple(input_data): + try: + return (True, int(input_data)) + except ValueError: + return (False, None) + + +def filelike_objects_are_closed_on_exception(filelike_object): + with filelike_object: + filelike_object.close() + raise Exception() From 2c9575a74575b2174de6c455747c016931f4aa7f Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Fri, 13 Oct 2017 11:34:30 -0500 Subject: [PATCH 08/49] error-handling: fixes for flake8 compliance --- exercises/error-handling/error_handling_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py index 0103431531..d80b06a0b0 100644 --- a/exercises/error-handling/error_handling_test.py +++ b/exercises/error-handling/error_handling_test.py @@ -29,7 +29,7 @@ def test_return_none(self): self.assertEqual(1, er.handle_error_by_returning_none('1'), 'Result of valid input should not be None') self.assertIsNone(er.handle_error_by_returning_none('a'), - 'Result of invalid input should be None') + 'Result of invalid input should be None') def test_return_tuple(self): successful_result, result = er.handle_error_by_returning_tuple('1') From 01e4ed94e4c0bb2437c18aaf32b705bb87218632 Mon Sep 17 00:00:00 2001 From: Gbekeloluwa Olufotebi Simeon Date: Sat, 14 Oct 2017 08:27:39 +0000 Subject: [PATCH 09/49] pangram: replace assertFalse with assertIs --- exercises/pangram/pangram_test.py | 41 +++++++++++++++++++------------ 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/exercises/pangram/pangram_test.py b/exercises/pangram/pangram_test.py index 09465a0228..72028650a6 100644 --- a/exercises/pangram/pangram_test.py +++ b/exercises/pangram/pangram_test.py @@ -7,39 +7,48 @@ class PangramTests(unittest.TestCase): def test_sentence_empty(self): - self.assertFalse(is_pangram('')) + self.assertIs(is_pangram(''), False) def test_pangram_with_only_lower_case(self): - self.assertTrue( - is_pangram('the quick brown fox jumps over the lazy dog')) + self.assertIs( + is_pangram('the quick brown fox jumps over the lazy dog'), + True) def test_missing_character_x(self): - self.assertFalse( + self.assertIs( is_pangram('a quick movement of the enemy will ' - 'jeopardize five gunboats')) + 'jeopardize five gunboats'), + False) def test_another_missing_character_x(self): - self.assertFalse( - is_pangram('the quick brown fish jumps over the lazy dog')) + self.assertIs( + is_pangram('the quick brown fish jumps over the lazy dog'), + False) def test_pangram_with_underscores(self): - self.assertTrue( - is_pangram('the_quick_brown_fox_jumps_over_the_lazy_dog')) + self.assertIs( + is_pangram('the_quick_brown_fox_jumps_over_the_lazy_dog'), + True) def test_pangram_with_numbers(self): - self.assertTrue( - is_pangram('the 1 quick brown fox jumps over the 2 lazy dogs')) + self.assertIs( + is_pangram('the 1 quick brown fox jumps over the 2 lazy dogs'), + True) def test_missing_letters_replaced_by_numbers(self): - self.assertFalse( - is_pangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog')) + self.assertIs( + is_pangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog'), + False) def test_pangram_with_mixedcase_and_punctuation(self): - self.assertTrue(is_pangram('"Five quacking Zephyrs jolt my wax bed."')) + self.assertIs( + is_pangram('"Five quacking Zephyrs jolt my wax bed."'), + True) def test_upper_and_lower_case_versions_of_the_same_character(self): - self.assertFalse( - is_pangram('the quick brown fox jumped over the lazy FOX')) + self.assertIs( + is_pangram('the quick brown fox jumped over the lazy FOX'), + False) if __name__ == '__main__': From 8fbb206e57762f6760c18b8e3b51fd432424bd81 Mon Sep 17 00:00:00 2001 From: clapmyhands Date: Wed, 18 Oct 2017 03:41:45 +0800 Subject: [PATCH 10/49] bracket-push: Add topics to resolve #852 --- config.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index fcf995781b..6408c3df36 100644 --- a/config.json +++ b/config.json @@ -570,7 +570,10 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "recursion", + "strings", + "stacks", + "parsing" ] }, { From 585c84857454781db6fec092c4133827f77fd712 Mon Sep 17 00:00:00 2001 From: clapmyhands Date: Wed, 18 Oct 2017 03:45:14 +0800 Subject: [PATCH 11/49] minesweeper: Add topics to resolve #853 --- config.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index fcf995781b..eefe4a6f46 100644 --- a/config.json +++ b/config.json @@ -580,7 +580,11 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "lists", + "parsing", + "transformation", + "loops", + "games" ] }, { From cf4992104f96c646304dfc91cda2a493850dffd6 Mon Sep 17 00:00:00 2001 From: clapmyhands Date: Wed, 18 Oct 2017 03:46:18 +0800 Subject: [PATCH 12/49] ocr-numbers: Add topics to resolve #857 --- config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index fcf995781b..17dfb0198e 100644 --- a/config.json +++ b/config.json @@ -629,7 +629,9 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "lists", + "parsing", + "pattern_recognition" ] }, { From 06e3866b5187978699ae443d534d965bc19bd5f3 Mon Sep 17 00:00:00 2001 From: "K.M. Tahsin Hassan Rahit" Date: Thu, 19 Oct 2017 21:47:43 +0600 Subject: [PATCH 13/49] circular-buffer: add topics --- config.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index cbbdb94ea7..96d472efb7 100644 --- a/config.json +++ b/config.json @@ -503,7 +503,11 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "algorithms", + "conditionals", + "classes", + "exception_handling", + "queues" ] }, { From 3277c6c82a5704d11437428678d66be8daa291da Mon Sep 17 00:00:00 2001 From: Mehul Prajapati Date: Thu, 19 Oct 2017 10:28:02 -0700 Subject: [PATCH 14/49] Added topics for Meetup --- config.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index cbbdb94ea7..c19403313a 100644 --- a/config.json +++ b/config.json @@ -140,7 +140,11 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "strings", + "conditionals", + "dates", + "parsing", + "pattern_recognition" ] }, { From 63de34488d48023643b6694e2350494ade3cc1c0 Mon Sep 17 00:00:00 2001 From: AJ Wallace Date: Thu, 19 Oct 2017 13:07:28 -0500 Subject: [PATCH 15/49] Update README.md: grammar change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b512745695..285d2cb695 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ This repo uses [flake8](http://flake8.readthedocs.org/en/latest/) with default s This repo uses `travis-ci` in the following configuration: [travis.yml](https://github.com/exercism/python/blob/master/.travis.yml) -It will check automatically the code style, the problem configuration and runns the unittests with all supported Python versions. +It will automatically check the code style, the problem configuration, and run the unittests with all supported Python versions. ## Pull Requests From f7b515c2ddeea64b902e1ba408d3e96f56e5521f Mon Sep 17 00:00:00 2001 From: Vibhu Kesar Date: Fri, 20 Oct 2017 01:15:45 +0530 Subject: [PATCH 16/49] Add topics: perfect numbers --- config.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index cbbdb94ea7..bd8120d4f0 100644 --- a/config.json +++ b/config.json @@ -582,7 +582,10 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "algorithms", + "loops", + "mathematics", + "logic" ] }, { From 31402db6173660a0be8e67f0366e2dfa98282c00 Mon Sep 17 00:00:00 2001 From: ferhat elmas Date: Thu, 19 Oct 2017 22:27:38 +0200 Subject: [PATCH 17/49] book-store: use book price const in calculation --- exercises/book-store/example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/book-store/example.py b/exercises/book-store/example.py index 4c7b69ae0d..4e3b598924 100644 --- a/exercises/book-store/example.py +++ b/exercises/book-store/example.py @@ -5,7 +5,7 @@ def _group_price(size): discounts = [0, .05, .1, .2, .25] if not (0 < size <= 5): raise ValueError('size must be in 1..' + len(discounts)) - return 8 * size * (1 - discounts[size - 1]) + return BOOK_PRICE * size * (1 - discounts[size - 1]) def calculate_total(books, price_so_far=0.): From 199e662e7782039c51476be471ede2a1a4878a97 Mon Sep 17 00:00:00 2001 From: Jason Edwards Date: Fri, 20 Oct 2017 15:59:43 +0100 Subject: [PATCH 18/49] Added license information to README.md Added license information to README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b512745695..dd9840a5e8 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,6 @@ If you're new to Git take a look at [this short guide](https://github.com/exerci ## Python icon The Python logo is an unregistered trademark. We are using a derived logo with the permission of the Python Software Foundation. + +## License +This repository uses the [MIT License](/LICENSE). From bb1dc608aa5a814a8eab4f938ac009ba6fe12c11 Mon Sep 17 00:00:00 2001 From: Mehul Prajapati Date: Sat, 21 Oct 2017 00:28:12 +0530 Subject: [PATCH 19/49] bob: Add topics to resolve #811 --- config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index bd8120d4f0..d5a515a03c 100644 --- a/config.json +++ b/config.json @@ -118,7 +118,9 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "strings", + "equality", + "conditionals" ] }, { From e95c51b24f20d7ce2e15699d8e6496519a0a6236 Mon Sep 17 00:00:00 2001 From: susg Date: Sat, 21 Oct 2017 01:59:29 +0530 Subject: [PATCH 20/49] Point-emulations: Add parameters to exercise placeholder Closes https://github.com/exercism/python/issues/610 --- exercises/point-mutations/point_mutations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/point-mutations/point_mutations.py b/exercises/point-mutations/point_mutations.py index 928f7d890c..5c7af56182 100644 --- a/exercises/point-mutations/point_mutations.py +++ b/exercises/point-mutations/point_mutations.py @@ -1,2 +1,2 @@ -def hamming_distance(): +def hamming_distance(dna_strand_1, dna_strand_2): pass From 1e287bb40dc1657f304efbc214b658f386e161f7 Mon Sep 17 00:00:00 2001 From: Tahsin Hassan Rahit Date: Sat, 21 Oct 2017 13:22:35 +0600 Subject: [PATCH 21/49] saddle-points: Add topics to resolve #846 --- config.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 8c8aaf950d..3da45a56a5 100644 --- a/config.json +++ b/config.json @@ -570,7 +570,13 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "algorithms", + "conditionals", + "lists", + "loops", + "mathematics", + "matrices", + "sets" ] }, { From f53450030ee67fd709170baedcc504390958e0cc Mon Sep 17 00:00:00 2001 From: LU4N Date: Sat, 21 Oct 2017 05:29:17 -0200 Subject: [PATCH 22/49] rail-fence-cipher: Add topics to resolve #844 --- config.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 3da45a56a5..0ec37bd56a 100644 --- a/config.json +++ b/config.json @@ -547,7 +547,10 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "algorithms", + "cryptography", + "text_formatting", + "lists" ] }, { From f8f495ca4a7b1e4ce038e02d3920feb1bd46fbbb Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 03:17:15 -0500 Subject: [PATCH 23/49] Improve exception tests by making use of Context Managers to resolve #477 --- README.md | 1 + exercises/binary-search/binary_search_test.py | 13 ++++++++----- exercises/binary/binary_test.py | 12 ++++++++---- exercises/meetup/meetup_test.py | 4 ++-- exercises/minesweeper/minesweeper_test.py | 9 ++++++--- exercises/ocr-numbers/ocr_numbers_test.py | 19 +++++++++++-------- .../pythagorean_triplet_test.py | 3 ++- exercises/saddle-points/saddle_points_test.py | 3 ++- exercises/simple-cipher/simple_cipher_test.py | 6 ++++-- exercises/triangle/triangle_test.py | 15 ++++++++++----- exercises/wordy/wordy_test.py | 13 ++++++++----- 11 files changed, 62 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index dbd93dd927..0649d23be3 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A list of missing exercise can be found here: http://exercism.io/languages/pytho - We use minimalistic stub files for all exercises (#272). - We use `unittest` (Python Standard Library) and no 3rd-party-framework. - We use the parameter order `self.assertEqual(actual, expected)` (#440). +- We use context managers (`with self.assertRaises(\):`) for testing for exceptions (#477). ### Testing diff --git a/exercises/binary-search/binary_search_test.py b/exercises/binary-search/binary_search_test.py index 96200e4ceb..c539d15f28 100644 --- a/exercises/binary-search/binary_search_test.py +++ b/exercises/binary-search/binary_search_test.py @@ -27,17 +27,20 @@ def test_finds_value_in_array_of_even_length(self): 5) def test_identifies_value_missing(self): - self.assertRaises(ValueError, binary_search, [1, 3, 4, 6, 8, 9, 11], 7) + with self.assertRaises(ValueError): + binary_search([1, 3, 4, 6, 8, 9, 11], 7) def test_value_smaller_than_arrays_minimum(self): - self.assertRaises(ValueError, binary_search, [1, 3, 4, 6, 8, 9, 11], 0) + with self.assertRaises(ValueError): + binary_search([1, 3, 4, 6, 8, 9, 11], 0) def test_value_larger_than_arrays_maximum(self): - self.assertRaises(ValueError, binary_search, [1, 3, 4, 6, 8, 9, 11], - 13) + with self.assertRaises(ValueError): + binary_search([1, 3, 4, 6, 8, 9, 11], 13) def test_empty_array(self): - self.assertRaises(ValueError, binary_search, [], 1) + with self.assertRaises(ValueError): + binary_search([], 1) if __name__ == '__main__': diff --git a/exercises/binary/binary_test.py b/exercises/binary/binary_test.py index 8254d7b549..28ce8fcdc0 100644 --- a/exercises/binary/binary_test.py +++ b/exercises/binary/binary_test.py @@ -32,16 +32,20 @@ def test_binary_10001101000_is_decimal_1128(self): self.assertEqual(parse_binary("10001101000"), 1128) def test_invalid_binary_text_only(self): - self.assertRaises(ValueError, parse_binary, "carrot") + with self.assertRaises(ValueError): + parse_binary("carrot") def test_invalid_binary_number_not_base2(self): - self.assertRaises(ValueError, parse_binary, "102011") + with self.assertRaises(ValueError): + parse_binary("102011") def test_invalid_binary_numbers_with_text(self): - self.assertRaises(ValueError, parse_binary, "10nope") + with self.assertRaises(ValueError): + parse_binary("10nope") def test_invalid_binary_text_with_numbers(self): - self.assertRaises(ValueError, parse_binary, "nope10") + with self.assertRaises(ValueError): + parse_binary("nope10") if __name__ == '__main__': diff --git a/exercises/meetup/meetup_test.py b/exercises/meetup/meetup_test.py index eb01a4bc32..4455bb8fbd 100644 --- a/exercises/meetup/meetup_test.py +++ b/exercises/meetup/meetup_test.py @@ -399,8 +399,8 @@ def test_fifth_monday_of_march_2015(self): meetup_day(2015, 3, 'Monday', '5th'), date(2015, 3, 30)) def test_nonexistent_fifth_monday_of_february_2015(self): - self.assertRaises(MeetupDayException, meetup_day, 2015, 2, 'Monday', - '5th') + with self.assertRaises(MeetupDayException): + meetup_day(2015, 2, 'Monday', '5th') if __name__ == '__main__': diff --git a/exercises/minesweeper/minesweeper_test.py b/exercises/minesweeper/minesweeper_test.py index f3518f3c67..542944bd04 100644 --- a/exercises/minesweeper/minesweeper_test.py +++ b/exercises/minesweeper/minesweeper_test.py @@ -141,19 +141,22 @@ def test_different_len(self): "|* |", "| |", "+-+"] - self.assertRaises(ValueError, board, inp) + with self.assertRaises(ValueError): + board(inp) def test_faulty_border(self): inp = ["+-----+", "* * |", "+-- --+"] - self.assertRaises(ValueError, board, inp) + with self.assertRaises(ValueError): + board(inp) def test_invalid_char(self): inp = ["+-----+", "|X * |", "+-----+"] - self.assertRaises(ValueError, board, inp) + with self.assertRaises(ValueError): + board(inp) if __name__ == '__main__': diff --git a/exercises/ocr-numbers/ocr_numbers_test.py b/exercises/ocr-numbers/ocr_numbers_test.py index 1fae0d1019..2be8935743 100644 --- a/exercises/ocr-numbers/ocr_numbers_test.py +++ b/exercises/ocr-numbers/ocr_numbers_test.py @@ -43,15 +43,17 @@ def test_unknown_char(self): " "]), '?') def test_too_short_row(self): - self.assertRaises(ValueError, number, [" ", - " _|", - " |", - " "]) + with self.assertRaises(ValueError): + number([" ", + " _|", + " |", + " "]) def test_insufficient_rows(self): - self.assertRaises(ValueError, number, [" ", - " _|", - " X|"]) + with self.assertRaises(ValueError): + number([" ", + " _|", + " X|"]) def test_grid0(self): self.assertEqual(grid('0'), [" _ ", @@ -114,7 +116,8 @@ def test_grid3186547290(self): ]) def test_invalid_grid(self): - self.assertRaises(ValueError, grid, '123a') + with self.assertRaises(ValueError): + grid('123a') if __name__ == '__main__': diff --git a/exercises/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/pythagorean-triplet/pythagorean_triplet_test.py index 4d9b29d10d..3c46fa5be3 100644 --- a/exercises/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/pythagorean-triplet/pythagorean_triplet_test.py @@ -80,7 +80,8 @@ def test_is_triplet3(self): self.assertIs(is_triplet((924, 43, 925)), True) def test_odd_number(self): - self.assertRaises(ValueError, primitive_triplets, 5) + with self.assertRaises(ValueError): + primitive_triplets(5) if __name__ == '__main__': diff --git a/exercises/saddle-points/saddle_points_test.py b/exercises/saddle-points/saddle_points_test.py index 2579b05474..065b24de74 100644 --- a/exercises/saddle-points/saddle_points_test.py +++ b/exercises/saddle-points/saddle_points_test.py @@ -28,7 +28,8 @@ def test_empty_matrix(self): def test_irregular_matrix(self): inp = [[3, 2, 1], [0, 1], [2, 1, 0]] - self.assertRaises(ValueError, saddle_points, inp) + with self.assertRaises(ValueError): + saddle_points(inp) if __name__ == '__main__': diff --git a/exercises/simple-cipher/simple_cipher_test.py b/exercises/simple-cipher/simple_cipher_test.py index fb0d51f9a1..d61cbc717d 100644 --- a/exercises/simple-cipher/simple_cipher_test.py +++ b/exercises/simple-cipher/simple_cipher_test.py @@ -68,8 +68,10 @@ def test_cipher_random_key(self): 'All items in the key must be chars and lowercase!') def test_cipher_wrong_key(self): - self.assertRaises(ValueError, Cipher, 'a1cde') - self.assertRaises(ValueError, Cipher, 'aBcde') + with self.assertRaises(ValueError): + Cipher('a1cde') + with self.assertRaises(ValueError): + Cipher('aBcde') if __name__ == '__main__': diff --git a/exercises/triangle/triangle_test.py b/exercises/triangle/triangle_test.py index 6566744f5c..38d0ba422f 100644 --- a/exercises/triangle/triangle_test.py +++ b/exercises/triangle/triangle_test.py @@ -34,19 +34,24 @@ def test_very_small_triangles_are_legal(self): self.assertEqual(Triangle(0.4, 0.6, 0.3).kind(), "scalene") def test_triangles_with_no_size_are_illegal(self): - self.assertRaises(TriangleError, Triangle, 0, 0, 0) + with self.assertRaises(TriangleError): + Triangle(0, 0, 0) def test_triangles_with_negative_sides_are_illegal(self): - self.assertRaises(TriangleError, Triangle, 3, 4, -5) + with self.assertRaises(TriangleError): + Triangle(3, 4, -5) def test_triangles_violating_triangle_inequality_are_illegal(self): - self.assertRaises(TriangleError, Triangle, 1, 1, 3) + with self.assertRaises(TriangleError): + Triangle(1, 1, 3) def test_triangles_violating_triangle_inequality_are_illegal_2(self): - self.assertRaises(TriangleError, Triangle, 2, 4, 2) + with self.assertRaises(TriangleError): + Triangle(2, 4, 2) def test_triangles_violating_triangle_inequality_are_illegal_3(self): - self.assertRaises(TriangleError, Triangle, 7, 3, 2) + with self.assertRaises(TriangleError): + Triangle(7, 3, 2) if __name__ == '__main__': diff --git a/exercises/wordy/wordy_test.py b/exercises/wordy/wordy_test.py index c35fe38529..588f190481 100644 --- a/exercises/wordy/wordy_test.py +++ b/exercises/wordy/wordy_test.py @@ -49,17 +49,20 @@ def test_divide_twice(self): calculate("What is -12000 divided by 25 divided by -30?"), 16) def test_invalid_operation(self): - self.assertRaises(ValueError, calculate, "What is 4 xor 7?") + with self.assertRaises(ValueError): + calculate("What is 4 xor 7?") def test_missing_operation(self): - self.assertRaises(ValueError, calculate, "What is 2 2 minus 3?") + with self.assertRaises(ValueError): + calculate("What is 2 2 minus 3?") def test_missing_number(self): - self.assertRaises(ValueError, calculate, - "What is 7 plus multiplied by -2?") + with self.assertRaises(ValueError): + calculate("What is 7 plus multiplied by -2?") def test_irrelevant_question(self): - self.assertRaises(ValueError, calculate, "Which is greater, 3 or 2?") + with self.assertRaises(ValueError): + calculate("Which is greater, 3 or 2?") if __name__ == '__main__': From c33528ee5185c8c4d26cdf4f47b1174a20d6f5bf Mon Sep 17 00:00:00 2001 From: Nathan Parsons Date: Sat, 21 Oct 2017 09:36:52 +0100 Subject: [PATCH 24/49] list-ops: Update test cases to v2.0.0 from canonical data --- exercises/list-ops/example.py | 39 ++------ exercises/list-ops/list_ops.py | 20 ++-- exercises/list-ops/list_ops_test.py | 141 +++++++++++----------------- 3 files changed, 73 insertions(+), 127 deletions(-) diff --git a/exercises/list-ops/example.py b/exercises/list-ops/example.py index e0cba98f39..ec5f2eb2d7 100644 --- a/exercises/list-ops/example.py +++ b/exercises/list-ops/example.py @@ -1,25 +1,21 @@ -def map_clone(function, xs): - return [function(elem) for elem in xs] +def append(xs, ys): + return concat([xs, ys]) -def length(xs): - return sum(1 for _ in xs) +def concat(lists): + return [elem for lst in lists for elem in lst] def filter_clone(function, xs): return [x for x in xs if function(x)] -def reverse(xs): - if not xs: - return [] - else: - return xs[::-1] +def length(xs): + return sum(1 for _ in xs) -def append(xs, y): - xs[len(xs):] = [y] - return xs +def map_clone(function, xs): + return [function(elem) for elem in xs] def foldl(function, xs, acc): @@ -36,20 +32,5 @@ def foldr(function, xs, acc): return function(xs[0], foldr(function, xs[1:], acc)) -def flat(xs): - out = [] - for item in xs: - if isinstance(item, list): - out.extend(flat(item)) - else: - out.append(item) - return out - - -def concat(xs, ys): - if not ys: - return xs - else: - for item in ys: - xs.append(item) - return xs +def reverse(xs): + return xs[::-1] diff --git a/exercises/list-ops/list_ops.py b/exercises/list-ops/list_ops.py index 9c03405352..33a2e6e1e1 100644 --- a/exercises/list-ops/list_ops.py +++ b/exercises/list-ops/list_ops.py @@ -1,34 +1,30 @@ -def map_clone(): +def append(xs, ys): pass -def length(): +def concat(lists): pass -def filter_clone(): +def filter_clone(function, xs): pass -def reverse(): +def length(xs): pass -def append(): +def map_clone(function, xs): pass -def foldl(): +def foldl(function, xs, acc): pass -def foldr(): +def foldr(function, xs, acc): pass -def flat(): - pass - - -def concat(): +def reverse(xs): pass diff --git a/exercises/list-ops/list_ops_test.py b/exercises/list-ops/list_ops_test.py index 14b11a2eeb..c96f6a599c 100644 --- a/exercises/list-ops/list_ops_test.py +++ b/exercises/list-ops/list_ops_test.py @@ -4,122 +4,91 @@ import list_ops +# Tests adapted from problem-specifications//canonical-data.json @ v2.0.0 + class ListOpsTest(unittest.TestCase): - # tests for map - def test_map_square(self): - self.assertEqual( - list_ops.map_clone(lambda x: x**2, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]) + # test for append + def test_append_empty_lists(self): + self.assertEqual(list_ops.append([], []), []) - def test_map_cube(self): - self.assertEqual( - list_ops.map_clone(lambda x: x**3, - [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]), - [-1, 8, -27, 64, -125, 216, -343, 512, -729, 1000]) + def test_append_empty_list_to_list(self): + self.assertEqual(list_ops.append([], [1, 2, 3, 4]), [1, 2, 3, 4]) - def test_map_absolute(self): - self.assertEqual( - list_ops.map_clone(lambda x: abs(x), - [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]), - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + def test_append_nonempty_lists(self): + self.assertEqual(list_ops.append([1, 2], [2, 3, 4, 5]), + [1, 2, 2, 3, 4, 5]) - def test_map_empty(self): - self.assertEqual(list_ops.map_clone(operator.index, []), []) + # tests for concat + def test_concat_empty_list(self): + self.assertEqual(list_ops.concat([]), []) - # tests for length - def test_pos_leng(self): - self.assertEqual( - list_ops.length([-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]), 10) + def test_concat_list_of_lists(self): + self.assertEqual(list_ops.concat([[1, 2], [3], [], [4, 5, 6]]), + [1, 2, 3, 4, 5, 6]) - def test_empty_len(self): - self.assertEqual(list_ops.length([]), 0) + # tests for filter_clone + def test_filter_empty_list(self): + self.assertEqual(list_ops.filter_clone(lambda x: x % 2 == 1, []), []) - # tests for filter - def test_filter_odd(self): + def test_filter_nonempty_list(self): self.assertEqual( - list_ops.filter_clone(lambda x: x % 2 != 0, [1, 2, 3, 4, 5, 6]), + list_ops.filter_clone(lambda x: x % 2 == 1, [1, 2, 3, 4, 5]), [1, 3, 5]) - def test_filter_even(self): - self.assertEqual( - list_ops.filter_clone(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6]), - [2, 4, 6]) - - # tests for reverse - def test_reverse_small(self): - self.assertEqual(list_ops.reverse([3, 2, 1]), [1, 2, 3]) - - def test_reverse_mixed_types(self): - self.assertEqual( - list_ops.reverse(["xyz", 4.0, "cat", 1]), [1, "cat", 4.0, "xyz"]) - - def test_reverse_empty(self): - self.assertEqual(list_ops.reverse([]), []) + # tests for length + def test_length_empty_list(self): + self.assertEqual(list_ops.length([]), 0) - # tests for append - def test_append_tuple(self): - self.assertEqual( - list_ops.append(["10", "python"], "hello"), - ["10", "python", "hello"]) + def test_length_nonempty_list(self): + self.assertEqual(list_ops.length([1, 2, 3, 4]), 4) - def test_append_range(self): - self.assertEqual( - list_ops.append([100], range(1000)), [100, range(1000)]) + # tests for map_clone + def test_map_empty_list(self): + self.assertEqual(list_ops.map_clone(lambda x: x + 1, []), []) - def test_append_to_empty(self): - self.assertEqual(list_ops.append([], 42), [42]) + def test_map_nonempty_list(self): + self.assertEqual(list_ops.map_clone(lambda x: x + 1, [1, 3, 5, 7]), + [2, 4, 6, 8]) # tests for foldl - def test_foldl_sum(self): - self.assertEqual( - list_ops.foldl(operator.add, [1, 2, 3, 4, 5, 6], 0), 21) + def test_foldl_empty_list(self): + self.assertEqual(list_ops.foldl(operator.mul, [], 2), 2) - def test_foldl_product(self): - self.assertEqual( - list_ops.foldl(operator.mul, [1, 2, 3, 4, 5, 6], 1), 720) + def test_foldl_nonempty_list_addition(self): + self.assertEqual(list_ops.foldl(operator.add, [1, 2, 3, 4], 5), 15) - def test_foldl_div(self): - self.assertEqual( - list_ops.foldl(operator.floordiv, [1, 2, 3, 4, 5, 6], 1), 0) - - def test_foldl_sub(self): - self.assertEqual(list_ops.foldl(operator.sub, [1, 2, 3, 4, 5], 0), -15) + def test_foldl_nonempty_list_floordiv(self): + self.assertEqual(list_ops.foldl(operator.floordiv, [2, 5], 5), 0) # tests for foldr - def test_foldr_sub(self): - self.assertEqual(list_ops.foldr(operator.sub, [1, 2, 3, 4, 5], 0), 3) + def test_foldr_empty_list(self): + self.assertEqual(list_ops.foldr(operator.mul, [], 2), 2) + + def test_foldr_nonempty_list_addition(self): + self.assertEqual(list_ops.foldr(operator.add, [1, 2, 3, 4], 5), 15) + + def test_foldr_nonempty_list_floordiv(self): + self.assertEqual(list_ops.foldr(operator.floordiv, [2, 5], 5), 2) + # additional test for foldr def test_foldr_add_str(self): self.assertEqual( list_ops.foldr(operator.add, ["e", "x", "e", "r", "c", "i", "s", "m"], "!"), "exercism!") - # tests for flatten - def test_flatten_nested(self): - self.assertEqual(list_ops.flat([[[1, 2], [3]], [[4]]]), [1, 2, 3, 4]) - - def test_flatten_once(self): - self.assertEqual(list_ops.flat([["x", "y", "z"]]), ["x", "y", "z"]) + # tests for reverse + def test_reverse_empty_list(self): + self.assertEqual(list_ops.reverse([]), []) - def test_flatten_empty(self): - self.assertEqual(list_ops.flat([]), []) + def test_reverse_nonempty_list(self): + self.assertEqual(list_ops.reverse([1, 3, 5, 7]), [7, 5, 3, 1]) - # tests for concat - def test_concat_two(self): - self.assertEqual( - list_ops.concat([1, 3, 5, 8], [9, 4, 5, 6]), - [1, 3, 5, 8, 9, 4, 5, 6]) - - def test_concat_nothing(self): + # additional test for reverse + def test_reverse_mixed_types(self): self.assertEqual( - list_ops.concat(['orange', 'apple', 'banana'], None), - ["orange", "apple", "banana"]) - - def test_concat_empty(self): - self.assertEqual(list_ops.concat([], []), []) + list_ops.reverse(["xyz", 4.0, "cat", 1]), [1, "cat", 4.0, "xyz"]) if __name__ == '__main__': From 6543fcd05bf91af74842fcb918076c45450cd3fc Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 10:35:13 -0500 Subject: [PATCH 25/49] error-handling: change topics to snake_case --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 348ae504d6..577c95bfd2 100644 --- a/config.json +++ b/config.json @@ -861,7 +861,7 @@ "unlocked_by": null, "difficulty": 3, "topics": [ - "exception handling" + "exception_handling" ] }, { From 6adccdea1905fcc6a8d83037d54482cb8ff4b0e9 Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 10:55:09 -0500 Subject: [PATCH 26/49] error-handling: remove incorrect Source section --- exercises/error-handling/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/exercises/error-handling/README.md b/exercises/error-handling/README.md index 9039642454..94adb62aea 100644 --- a/exercises/error-handling/README.md +++ b/exercises/error-handling/README.md @@ -19,9 +19,5 @@ For example, if you're submitting `bob.py` for the Bob exercise, the submit comm For more detailed information about running tests, code style and linting, please see the [help page](http://exercism.io/languages/python). -## Source - -Problem 6 at Project Euler [http://projecteuler.net/problem=6](http://projecteuler.net/problem=6) - ## Submitting Incomplete Solutions It's possible to submit an incomplete solution so you can see how others have completed the exercise. From 68b8f22c75fbbd5a83c8a426eb4fdf99d41e5e0a Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 11:08:01 -0500 Subject: [PATCH 27/49] error-handling: improve use of context manager --- .../error-handling/error_handling_test.py | 23 +++++++++++++++---- exercises/error-handling/example.py | 7 ++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py index d80b06a0b0..dff2baf391 100644 --- a/exercises/error-handling/error_handling_test.py +++ b/exercises/error-handling/error_handling_test.py @@ -6,18 +6,26 @@ class FileLike(object): def __init__(self): self.is_open = False + self.was_open = False + self.did_something = False def open(self): - self.is_open = True - - def __enter__(self): + self.was_open = False self.is_open = True def close(self): self.is_open = False + self.was_open = True + + def __enter__(self): + self.open() def __exit__(self): - self.is_open = False + self.close() + + def do_something(self): + self.did_something = True + raise Exception() class ErrorHandlingTest(unittest.TestCase): @@ -45,7 +53,12 @@ def test_filelike_objects_are_closed_on_exception(self): filelike_object.open() with self.assertRaises(Exception): er.filelike_objects_are_closed_on_exception(filelike_object) - self.assertFalse(filelike_object.is_open) + self.assertFalse(filelike_object.is_open, + 'filelike_object should be closed') + self.assertTrue(filelike_object.was_open, + 'filelike_object should have been opened') + self.assertTrue(filelike_object.did_something, + 'filelike_object should call did_something()') if __name__ == '__main__': diff --git a/exercises/error-handling/example.py b/exercises/error-handling/example.py index 837f678380..ba146f1306 100644 --- a/exercises/error-handling/example.py +++ b/exercises/error-handling/example.py @@ -18,5 +18,8 @@ def handle_error_by_returning_tuple(input_data): def filelike_objects_are_closed_on_exception(filelike_object): with filelike_object: - filelike_object.close() - raise Exception() + try: + filelike_object.do_something() + except Exception: + filelike_object.close() + raise From ebe556e5a7530b42729719bfa03ac1dc69e83fcb Mon Sep 17 00:00:00 2001 From: Gbekeloluwa Olufotebi Date: Sat, 21 Oct 2017 16:25:53 +0000 Subject: [PATCH 28/49] allergies: Replace assertFalse with assertIs --- exercises/allergies/allergies_test.py | 14 +++++++------- exercises/allergies/example.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exercises/allergies/allergies_test.py b/exercises/allergies/allergies_test.py index b7d62f5205..fcdd9c2f7c 100644 --- a/exercises/allergies/allergies_test.py +++ b/exercises/allergies/allergies_test.py @@ -12,18 +12,18 @@ class AllergiesTests(unittest.TestCase): def test_no_allergies_means_not_allergic(self): allergies = Allergies(0) - self.assertFalse(allergies.is_allergic_to('peanuts')) - self.assertFalse(allergies.is_allergic_to('cats')) - self.assertFalse(allergies.is_allergic_to('strawberries')) + self.assertIs(allergies.is_allergic_to('peanuts'), False) + self.assertIs(allergies.is_allergic_to('cats'), False) + self.assertIs(allergies.is_allergic_to('strawberries'), False) def test_is_allergic_to_eggs(self): - self.assertTrue(Allergies(1).is_allergic_to('eggs')) + self.assertIs(Allergies(1).is_allergic_to('eggs'), True) def test_allergic_to_eggs_in_addition_to_other_stuff(self): allergies = Allergies(5) - self.assertTrue(allergies.is_allergic_to('eggs')) - self.assertTrue(allergies.is_allergic_to('shellfish')) - self.assertFalse(allergies.is_allergic_to('strawberries')) + self.assertIs(allergies.is_allergic_to('eggs'), True) + self.assertIs(allergies.is_allergic_to('shellfish'), True) + self.assertIs(allergies.is_allergic_to('strawberries'), False) def test_no_allergies_at_all(self): self.assertEqual(Allergies(0).lst, []) diff --git a/exercises/allergies/example.py b/exercises/allergies/example.py index 2e4bb090de..a88674ca97 100644 --- a/exercises/allergies/example.py +++ b/exercises/allergies/example.py @@ -15,7 +15,7 @@ def __init__(self, score): self.score = score def is_allergic_to(self, allergy): - return self.score & 1 << self._allergies.index(allergy) + return bool(self.score & 1 << self._allergies.index(allergy)) @property def lst(self): From c8defd903d32f65ad1fa7d2b1d8053c53019c7e0 Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 11:29:33 -0500 Subject: [PATCH 29/49] clock: Replace Clock.add() with magic method __add__() to resolve #727 --- exercises/clock/clock.py | 3 +++ exercises/clock/clock_test.py | 32 ++++++++++++++++---------------- exercises/clock/example.py | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/exercises/clock/clock.py b/exercises/clock/clock.py index 3044410f43..afd470e62d 100644 --- a/exercises/clock/clock.py +++ b/exercises/clock/clock.py @@ -1,3 +1,6 @@ class Clock(object): def __init__(self, hour, minute): pass + + def __add__(self, other): + pass diff --git a/exercises/clock/clock_test.py b/exercises/clock/clock_test.py index 444e30021d..07b23e75c8 100644 --- a/exercises/clock/clock_test.py +++ b/exercises/clock/clock_test.py @@ -64,52 +64,52 @@ def test_negative_hour_and_minutes_both_roll_over_continuously(self): # Test adding and subtracting minutes. def test_add_minutes(self): - self.assertEqual(str(Clock(10, 0).add(3)), '10:03') + self.assertEqual(str(Clock(10, 0) + 3), '10:03') def test_add_no_minutes(self): - self.assertEqual(str(Clock(6, 41).add(0)), '06:41') + self.assertEqual(str(Clock(6, 41) + 0), '06:41') def test_add_to_next_hour(self): - self.assertEqual(str(Clock(0, 45).add(40)), '01:25') + self.assertEqual(str(Clock(0, 45) + 40), '01:25') def test_add_more_than_one_hour(self): - self.assertEqual(str(Clock(10, 0).add(61)), '11:01') + self.assertEqual(str(Clock(10, 0) + 61), '11:01') def test_add_more_than_two_hours_with_carry(self): - self.assertEqual(str(Clock(0, 45).add(160)), '03:25') + self.assertEqual(str(Clock(0, 45) + 160), '03:25') def test_add_across_midnight(self): - self.assertEqual(str(Clock(23, 59).add(2)), '00:01') + self.assertEqual(str(Clock(23, 59) + 2), '00:01') def test_add_more_than_one_day(self): - self.assertEqual(str(Clock(5, 32).add(1500)), '06:32') + self.assertEqual(str(Clock(5, 32) + 1500), '06:32') def test_add_more_than_two_days(self): - self.assertEqual(str(Clock(1, 1).add(3500)), '11:21') + self.assertEqual(str(Clock(1, 1) + 3500), '11:21') def test_subtract_minutes(self): - self.assertEqual(str(Clock(10, 3).add(-3)), '10:00') + self.assertEqual(str(Clock(10, 3) + -3), '10:00') def test_subtract_to_previous_hour(self): - self.assertEqual(str(Clock(10, 3).add(-3)), '10:00') + self.assertEqual(str(Clock(10, 3) + -3), '10:00') def test_subtract_more_than_an_hour(self): - self.assertEqual(str(Clock(10, 3).add(-30)), '09:33') + self.assertEqual(str(Clock(10, 3) + -30), '09:33') def test_subtract_across_midnight(self): - self.assertEqual(str(Clock(10, 3).add(-70)), '08:53') + self.assertEqual(str(Clock(10, 3) + -70), '08:53') def test_subtract_more_than_two_hours(self): - self.assertEqual(str(Clock(0, 0).add(-160)), '21:20') + self.assertEqual(str(Clock(0, 0) + -160), '21:20') def test_subtract_more_than_two_hours_with_borrow(self): - self.assertEqual(str(Clock(6, 15).add(-160)), '03:35') + self.assertEqual(str(Clock(6, 15) + -160), '03:35') def test_subtract_more_than_one_day(self): - self.assertEqual(str(Clock(5, 32).add(-1500)), '04:32') + self.assertEqual(str(Clock(5, 32) + -1500), '04:32') def test_subtract_more_than_two_days(self): - self.assertEqual(str(Clock(2, 20).add(-3000)), '00:20') + self.assertEqual(str(Clock(2, 20) + -3000), '00:20') # Construct two separate clocks, set times, test if they are equal. def test_clocks_with_same_time(self): diff --git a/exercises/clock/example.py b/exercises/clock/example.py index 4b80c2f6a7..27c7e16b40 100644 --- a/exercises/clock/example.py +++ b/exercises/clock/example.py @@ -13,7 +13,7 @@ def __repr__(self): def __eq__(self, other): return repr(self) == repr(other) - def add(self, minutes): + def __add__(self, minutes): self.minute += minutes return self.cleanup() From 01708a6ca8c7559b1b488e56dce8c79286dfc521 Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 11:46:02 -0500 Subject: [PATCH 30/49] Add exercise zipper to resolve #736 --- config.json | 12 +++++ exercises/zipper/README.md | 43 +++++++++++++++++ exercises/zipper/example.py | 41 +++++++++++++++++ exercises/zipper/zipper.py | 28 +++++++++++ exercises/zipper/zipper_test.py | 82 +++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 exercises/zipper/README.md create mode 100644 exercises/zipper/example.py create mode 100644 exercises/zipper/zipper.py create mode 100644 exercises/zipper/zipper_test.py diff --git a/config.json b/config.json index 1e11511552..bc309f0e43 100644 --- a/config.json +++ b/config.json @@ -1074,6 +1074,18 @@ "conditionals" ] }, + { + "uuid": "4f5f890d-0db4-5480-f79a-21057c37871b15133dc", + "slug": "zipper", + "core": false, + "unlocked_by": null, + "difficulty": 8, + "topics": [ + "recursion", + "searching", + "trees" + ] + }, { "uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd", "slug": "accumulate", diff --git a/exercises/zipper/README.md b/exercises/zipper/README.md new file mode 100644 index 0000000000..d1241c229c --- /dev/null +++ b/exercises/zipper/README.md @@ -0,0 +1,43 @@ +# Zipper + +Creating a zipper for a binary tree. + +[Zippers](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) are +a purely functional way of navigating within a data structure and +manipulating it. They essentially contain a data structure and a +pointer into that data structure (called the focus). + +For example given a rose tree (where each node contains a value and a +list of child nodes) a zipper might support these operations: + +- `from_tree` (get a zipper out of a rose tree, the focus is on the root node) +- `to_tree` (get the rose tree out of the zipper) +- `value` (get the value of the focus node) +- `prev` (move the focus to the previous child of the same parent, + returns a new zipper) +- `next` (move the focus to the next child of the same parent, returns a + new zipper) +- `up` (move the focus to the parent, returns a new zipper) +- `set_value` (set the value of the focus node, returns a new zipper) +- `insert_before` (insert a new subtree before the focus node, it + becomes the `prev` of the focus node, returns a new zipper) +- `insert_after` (insert a new subtree after the focus node, it becomes + the `next` of the focus node, returns a new zipper) +- `delete` (removes the focus node and all subtrees, focus moves to the + `next` node if possible otherwise to the `prev` node if possible, + otherwise to the parent node, returns a new zipper) + +### 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/zipper/example.py b/exercises/zipper/example.py new file mode 100644 index 0000000000..a9003e9e21 --- /dev/null +++ b/exercises/zipper/example.py @@ -0,0 +1,41 @@ +class Zipper(object): + @staticmethod + def from_tree(tree): + return Zipper(dict(tree), []) + + def __init__(self, tree, ancestors): + self.tree = tree + self.ancestors = ancestors + + def value(self): + return self.tree['value'] + + def set_value(self, value): + self.tree['value'] = value + return self + + def left(self): + if self.tree['left'] is None: + return None + return Zipper(self.tree['left'], self.ancestors + [self.tree]) + + def set_left(self, tree): + self.tree['left'] = tree + return self + + def right(self): + if self.tree['right'] is None: + return None + return Zipper(self.tree['right'], self.ancestors + [self.tree]) + + def set_right(self, tree): + self.tree['right'] = tree + return self + + def up(self): + return Zipper(self.ancestors[-1], self.ancestors[:-1]) + + def to_tree(self): + if any(self.ancestors): + return self.ancestors[0] + return self.tree diff --git a/exercises/zipper/zipper.py b/exercises/zipper/zipper.py new file mode 100644 index 0000000000..14903a8a09 --- /dev/null +++ b/exercises/zipper/zipper.py @@ -0,0 +1,28 @@ +class Zipper(object): + @staticmethod + def from_tree(tree): + pass + + def value(self): + pass + + def set_value(self): + pass + + def left(self): + pass + + def set_left(self): + pass + + def right(self): + pass + + def set_right(self): + pass + + def up(self): + pass + + def to_tree(self): + pass diff --git a/exercises/zipper/zipper_test.py b/exercises/zipper/zipper_test.py new file mode 100644 index 0000000000..0e2a0a7ef7 --- /dev/null +++ b/exercises/zipper/zipper_test.py @@ -0,0 +1,82 @@ +import unittest + +from zipper import Zipper + +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 + + +def bt(value, left, right): + return { + 'value': value, + 'left': left, + 'right': right + } + + +def leaf(value): + return bt(value, None, None) + + +EMPTY_TREE = None + + +def create_trees(): + t1 = bt(1, bt(2, EMPTY_TREE, leaf(3)), leaf(4)) + t2 = bt(1, bt(5, EMPTY_TREE, leaf(3)), leaf(4)) + t3 = bt(1, bt(2, leaf(5), leaf(3)), leaf(4)) + t4 = bt(1, leaf(2), leaf(4)) + return (t1, t2, t3, t4) + + +class ZipperTest(unittest.TestCase): + def test_data_is_retained(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + tree = zipper.to_tree() + self.assertEqual(tree, t1) + + def test_left_and_right_value(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertEqual(zipper.left().right().value(), 3) + + def test_dead_end(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertIsNone(zipper.left().left()) + + def test_tree_from_deep_focus(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertEqual(zipper.left().right().to_tree(), t1) + + def test_set_value(self): + t1, t2, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + updatedZipper = zipper.left().set_value(5) + tree = updatedZipper.to_tree() + self.assertEqual(tree, t2) + + def test_set_left_with_value(self): + t1, _, t3, _ = create_trees() + zipper = Zipper.from_tree(t1) + updatedZipper = zipper.left().set_left(leaf(5)) + tree = updatedZipper.to_tree() + self.assertEqual(tree, t3) + + def test_set_right_to_none(self): + t1, _, _, t4 = create_trees() + zipper = Zipper.from_tree(t1) + updatedZipper = zipper.left().set_right(None) + tree = updatedZipper.to_tree() + self.assertEqual(tree, t4) + + def test_different_paths_to_same_zipper(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertEqual(zipper.left().up().right().to_tree(), + zipper.right().to_tree()) + + +if __name__ == '__main__': + unittest.main() From 24c7f7947905443ecce2203f509ef5e1d981b377 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 23 Oct 2017 12:00:06 -0500 Subject: [PATCH 31/49] error-handling: further improve context manager implementation --- exercises/error-handling/error_handling_test.py | 6 +++--- exercises/error-handling/example.py | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py index dff2baf391..f44525c4ef 100644 --- a/exercises/error-handling/error_handling_test.py +++ b/exercises/error-handling/error_handling_test.py @@ -19,8 +19,9 @@ def close(self): def __enter__(self): self.open() + return self - def __exit__(self): + def __exit__(self, *args): self.close() def do_something(self): @@ -50,7 +51,6 @@ def test_return_tuple(self): def test_filelike_objects_are_closed_on_exception(self): filelike_object = FileLike() - filelike_object.open() with self.assertRaises(Exception): er.filelike_objects_are_closed_on_exception(filelike_object) self.assertFalse(filelike_object.is_open, @@ -58,7 +58,7 @@ def test_filelike_objects_are_closed_on_exception(self): self.assertTrue(filelike_object.was_open, 'filelike_object should have been opened') self.assertTrue(filelike_object.did_something, - 'filelike_object should call did_something()') + 'filelike_object should call do_something()') if __name__ == '__main__': diff --git a/exercises/error-handling/example.py b/exercises/error-handling/example.py index ba146f1306..b882423e39 100644 --- a/exercises/error-handling/example.py +++ b/exercises/error-handling/example.py @@ -17,9 +17,8 @@ def handle_error_by_returning_tuple(input_data): def filelike_objects_are_closed_on_exception(filelike_object): - with filelike_object: + with filelike_object as fobj: try: - filelike_object.do_something() - except Exception: - filelike_object.close() - raise + fobj.do_something() + finally: + fobj.close() From bc7eb8889f6a1f402d2429c4aa364216ef485efc Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 23 Oct 2017 14:00:09 -0500 Subject: [PATCH 32/49] error-handling: remove redundant error handling inside "with" --- exercises/error-handling/example.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/exercises/error-handling/example.py b/exercises/error-handling/example.py index b882423e39..8544114809 100644 --- a/exercises/error-handling/example.py +++ b/exercises/error-handling/example.py @@ -18,7 +18,4 @@ def handle_error_by_returning_tuple(input_data): def filelike_objects_are_closed_on_exception(filelike_object): with filelike_object as fobj: - try: - fobj.do_something() - finally: - fobj.close() + fobj.do_something() From 13839e9974f56dbc1683f4dda67b68bd47da2bbc Mon Sep 17 00:00:00 2001 From: j james Date: Mon, 23 Oct 2017 14:43:17 -0500 Subject: [PATCH 33/49] Updated README.md added comma "If you're new to Git, take a look at..." --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0649d23be3..93e5983408 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Please try to follow the [The seven rules of a great Git commit message](https:/ If you're interested, Tim Pope even has an [entire blog post](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) on good commit messages. -If you're new to Git take a look at [this short guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md#git-basics). +If you're new to Git, take a look at [this short guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md#git-basics). ## Python icon From 9cef07646dd0a3310fdaf5f210a15edeae05d41c Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 23 Oct 2017 14:52:18 -0500 Subject: [PATCH 34/49] markdown: Do not add

to list items --- exercises/markdown/example.py | 2 +- exercises/markdown/markdown_test.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/markdown/example.py b/exercises/markdown/example.py index 7321ad6f34..dbdb77b06c 100644 --- a/exercises/markdown/example.py +++ b/exercises/markdown/example.py @@ -66,7 +66,7 @@ def parse_line(line, in_list): if not re.match(')?

  • _(.*)', res): + if list_match is None: res = re.sub('(.*)(
  • )(.*)(
  • )(.*)', r'\1\2

    \3

    \4\5', res) while check_bold(res): diff --git a/exercises/markdown/markdown_test.py b/exercises/markdown/markdown_test.py index 6042594ff3..1cea371896 100644 --- a/exercises/markdown/markdown_test.py +++ b/exercises/markdown/markdown_test.py @@ -2,7 +2,7 @@ from markdown import parse_markdown -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 class TestMarkdown(unittest.TestCase): @@ -37,8 +37,8 @@ def test_h6(self): def test_unordered_lists(self): self.assertEqual(parse_markdown('* Item 1\n* Item 2'), - '
    • Item 1

    • ' - '
    • Item 2

    ') + '
    • Item 1
    • ' + '
    • Item 2
    ') def test_little_bit_of_everything(self): self.assertEqual(parse_markdown( From 5cf1ec25a1463ffb8d85e0804f272a27c491a650 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 23 Oct 2017 15:06:44 -0500 Subject: [PATCH 35/49] forth: fix import line --- exercises/forth/forth.py | 4 ++++ exercises/forth/forth_test.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/exercises/forth/forth.py b/exercises/forth/forth.py index f8362ec7f4..7cf4ff3b40 100644 --- a/exercises/forth/forth.py +++ b/exercises/forth/forth.py @@ -1,2 +1,6 @@ +class StackUnderflowError(Exception): + pass + + def evaluate(input_data): pass diff --git a/exercises/forth/forth_test.py b/exercises/forth/forth_test.py index 66a4ec819a..f8dac88bf6 100644 --- a/exercises/forth/forth_test.py +++ b/exercises/forth/forth_test.py @@ -1,6 +1,6 @@ import unittest -from example import evaluate, StackUnderflowError +from forth import evaluate, StackUnderflowError # test cases adapted from `x-common//canonical-data.json` @ version: 1.2.0 From ce8c579a2e4fd6132e606ca5bbe372d67a3688ce Mon Sep 17 00:00:00 2001 From: Zac Kwan Date: Tue, 24 Oct 2017 14:59:21 +0800 Subject: [PATCH 36/49] grains: Add topics to resolve #830 --- config.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index bc309f0e43..9d4a1bdbfe 100644 --- a/config.json +++ b/config.json @@ -373,7 +373,10 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - + "bitwise_operations", + "integers", + "mathematics", + "type_conversion" ] }, { From fb26a66ab179fe375f50933c2be3f2e5da2f68a3 Mon Sep 17 00:00:00 2001 From: Hazel Seanor Date: Tue, 24 Oct 2017 14:44:48 +0100 Subject: [PATCH 37/49] Swap '2016' for '1996' to stop faulty logic from passing unit test [This leap solution](http://exercism.io/submissions/7ef5b0ab93b540f79cdee9f153e0a21c) should not pass the unit test, since it checks whether `year % 100` and `year % 400` are equal in order to tell whether the year it has been passed is a leap year. This works for 2016 because `2016 % 100` and `2016 % 400` both evaluate to 16. 1996, another leap year that is divisible by 4 but not 100, does not have this property and, under that solution, would falsely not be classed as a leap year. --- exercises/leap/leap_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/leap/leap_test.py b/exercises/leap/leap_test.py index 1936a1a136..5b21149d9d 100644 --- a/exercises/leap/leap_test.py +++ b/exercises/leap/leap_test.py @@ -10,7 +10,7 @@ def test_year_not_divisible_by_4(self): self.assertIs(is_leap_year(2015), False) def test_year_divisible_by_4_not_divisible_by_100(self): - self.assertIs(is_leap_year(2016), True) + self.assertIs(is_leap_year(1996), True) def test_year_divisible_by_100_not_divisible_by_400(self): self.assertIs(is_leap_year(2100), False) From 19351fe149403d87e7d128fb31a878ea7c342789 Mon Sep 17 00:00:00 2001 From: Hazel Seanor Date: Tue, 24 Oct 2017 17:14:04 +0100 Subject: [PATCH 38/49] Changed version number --- exercises/leap/leap_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/leap/leap_test.py b/exercises/leap/leap_test.py index 5b21149d9d..68cd970057 100644 --- a/exercises/leap/leap_test.py +++ b/exercises/leap/leap_test.py @@ -3,7 +3,7 @@ from leap import is_leap_year -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0 class YearTest(unittest.TestCase): def test_year_not_divisible_by_4(self): From d01e63ea322f918cac6108e941619f2e3ec456e6 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Tue, 24 Oct 2017 11:16:47 -0500 Subject: [PATCH 39/49] error-handling: replace assertTrue and assertFalse with assertIs --- .../error-handling/error_handling_test.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py index f44525c4ef..de4ab41a4d 100644 --- a/exercises/error-handling/error_handling_test.py +++ b/exercises/error-handling/error_handling_test.py @@ -42,23 +42,24 @@ def test_return_none(self): def test_return_tuple(self): successful_result, result = er.handle_error_by_returning_tuple('1') - self.assertTrue(successful_result, 'Valid input should be successful') + self.assertIs(True, successful_result, + 'Valid input should be successful') self.assertEqual(1, result, 'Result of valid input should not be None') failure_result, result = er.handle_error_by_returning_tuple('a') - self.assertFalse(failure_result, - 'Invalid input should not be successful') + self.assertIs(False, failure_result, + 'Invalid input should not be successful') def test_filelike_objects_are_closed_on_exception(self): filelike_object = FileLike() with self.assertRaises(Exception): er.filelike_objects_are_closed_on_exception(filelike_object) - self.assertFalse(filelike_object.is_open, - 'filelike_object should be closed') - self.assertTrue(filelike_object.was_open, - 'filelike_object should have been opened') - self.assertTrue(filelike_object.did_something, - 'filelike_object should call do_something()') + self.assertIs(False, filelike_object.is_open, + 'filelike_object should be closed') + self.assertIs(True, filelike_object.was_open, + 'filelike_object should have been opened') + self.assertIs(True, filelike_object.did_something, + 'filelike_object should call do_something()') if __name__ == '__main__': From 83e89f3ccdd389e932cc5767037553d94a594f1a Mon Sep 17 00:00:00 2001 From: Greg Bullmer Date: Tue, 24 Oct 2017 11:39:37 -0500 Subject: [PATCH 40/49] complex-numbers: Fix spelling mistake (#982) complex-numbers: Fix spelling mistake --- exercises/complex-numbers/complex_numbers_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/complex-numbers/complex_numbers_test.py b/exercises/complex-numbers/complex_numbers_test.py index 08b14238cf..cad5d9670d 100644 --- a/exercises/complex-numbers/complex_numbers_test.py +++ b/exercises/complex-numbers/complex_numbers_test.py @@ -55,7 +55,7 @@ def test_subtract_purely_real_numbers(self): self.assertEqual(first_input.sub(second_input).real, 2) self.assertEqual(first_input.sub(second_input).imaginary, 0) - def test_substract_numbers_with_real_and_imaginary_part(self): + def test_subtract_numbers_with_real_and_imaginary_part(self): first_input = ComplexNumber(1, 2) second_input = ComplexNumber(-2, -2) self.assertEqual(first_input.sub(second_input).real, 3) From 55b13a2d22d50cf4c12051b6150351c1ec55b0b6 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Tue, 24 Oct 2017 11:56:52 -0500 Subject: [PATCH 41/49] error-handling: conform to parameter order convention --- exercises/error-handling/error_handling_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/exercises/error-handling/error_handling_test.py b/exercises/error-handling/error_handling_test.py index de4ab41a4d..b29bbfa050 100644 --- a/exercises/error-handling/error_handling_test.py +++ b/exercises/error-handling/error_handling_test.py @@ -35,30 +35,30 @@ def test_throw_exception(self): er.handle_error_by_throwing_exception() def test_return_none(self): - self.assertEqual(1, er.handle_error_by_returning_none('1'), + self.assertEqual(er.handle_error_by_returning_none('1'), 1, 'Result of valid input should not be None') self.assertIsNone(er.handle_error_by_returning_none('a'), 'Result of invalid input should be None') def test_return_tuple(self): successful_result, result = er.handle_error_by_returning_tuple('1') - self.assertIs(True, successful_result, + self.assertIs(successful_result, True, 'Valid input should be successful') - self.assertEqual(1, result, 'Result of valid input should not be None') + self.assertEqual(result, 1, 'Result of valid input should not be None') failure_result, result = er.handle_error_by_returning_tuple('a') - self.assertIs(False, failure_result, + self.assertIs(failure_result, False, 'Invalid input should not be successful') def test_filelike_objects_are_closed_on_exception(self): filelike_object = FileLike() with self.assertRaises(Exception): er.filelike_objects_are_closed_on_exception(filelike_object) - self.assertIs(False, filelike_object.is_open, + self.assertIs(filelike_object.is_open, False, 'filelike_object should be closed') - self.assertIs(True, filelike_object.was_open, + self.assertIs(filelike_object.was_open, True, 'filelike_object should have been opened') - self.assertIs(True, filelike_object.did_something, + self.assertIs(filelike_object.did_something, True, 'filelike_object should call do_something()') From 8111184dbfb99ea5cc6dd9812a8e6a84d473d87e Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Wed, 25 Oct 2017 01:09:29 +0800 Subject: [PATCH 42/49] twelve-days: Add parameters to exercise placeholder to resolve #649 --- exercises/twelve-days/example.py | 6 +++--- exercises/twelve-days/twelve_days.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/twelve-days/example.py b/exercises/twelve-days/example.py index 029a142750..ccfc0c0c60 100644 --- a/exercises/twelve-days/example.py +++ b/exercises/twelve-days/example.py @@ -15,13 +15,13 @@ 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth'] -def verse(n): - gifts = GIFTS[-n:] +def verse(day_number): + gifts = GIFTS[-day_number:] if len(gifts) > 1: gifts[:-1] = [', '.join(gifts[:-1])] gifts = ', and '.join(gifts) return 'On the {} day of Christmas my true love gave to me, {}.\n'.format( - ORDINAL[n], gifts) + ORDINAL[day_number], gifts) def verses(start, end): diff --git a/exercises/twelve-days/twelve_days.py b/exercises/twelve-days/twelve_days.py index 0d17418086..90a02988cd 100644 --- a/exercises/twelve-days/twelve_days.py +++ b/exercises/twelve-days/twelve_days.py @@ -1,8 +1,8 @@ -def verse(): +def verse(day_number): pass -def verses(): +def verses(start, end): pass From b3ba31de05ce83af166f5714be1b0c4d11de2dd5 Mon Sep 17 00:00:00 2001 From: Lucas Lois Date: Tue, 24 Oct 2017 17:39:16 -0300 Subject: [PATCH 43/49] Adds hints to cryptography/randomness exercises (#967) Adds HINTS file to "rail-fence-cipher" Adds HINTS file to "simple-cipher" Adds HINTS file to "rotational-cipher" Adds HINTS file to "robot-name" --- exercises/diffie-hellman/.meta/hints.md | 16 ++++++++++++++++ exercises/diffie-hellman/README.md | 17 +++++++++++++++-- exercises/simple-cipher/.meta/hints.md | 16 ++++++++++++++++ exercises/simple-cipher/README.md | 17 +++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 exercises/diffie-hellman/.meta/hints.md create mode 100644 exercises/simple-cipher/.meta/hints.md diff --git a/exercises/diffie-hellman/.meta/hints.md b/exercises/diffie-hellman/.meta/hints.md new file mode 100644 index 0000000000..c3f8575374 --- /dev/null +++ b/exercises/diffie-hellman/.meta/hints.md @@ -0,0 +1,16 @@ +## Should I use random or secrets? + +Python, as of version 3.6, includes two different random modules. + +The module called `random` is pseudo-random, meaning it does not generate +true randomness, but follows an algorithm that simulates randomness. +Since random numbers are generated through a known algorithm, they are not truly random. + +The `random` module is not correctly suited for cryptography and should not be used, +precisely because it is pseudo-random. + +For this reason, in version 3.6, Python introduced the `secrets` module, which generates +cryptographically strong random numbers that provide the greater security required for cryptography. + +Since this is only an exercise, `random` is fine to use, but note that **it would be +very insecure if actually used for cryptography.** diff --git a/exercises/diffie-hellman/README.md b/exercises/diffie-hellman/README.md index 0a793540a7..d4a5f867a0 100644 --- a/exercises/diffie-hellman/README.md +++ b/exercises/diffie-hellman/README.md @@ -37,9 +37,22 @@ Bob calculates The calculations produce the same result! Alice and Bob now share secret s. -## Notes +## Should I use random or secrets? -Python, as of version 3.6, includes two different random modules. The module called `random` is pseudo-random, meaning it does not generate true randomness, but follows and algorithm that simulates randomness. Since random numbers are generated through a known algorithm, they are not truly random. The `random` module is not correctly suited for cryptography and should not be used, because it is pseudo-random. In version 3.6, Python introduced the `secrets` module, which generates cryptographically strong random numbers that provide the greater security required for cryptography. Since this is only an exercise, `random` is fine to use, but note that it would be very insecure if actually used for cryptography. +Python, as of version 3.6, includes two different random modules. + +The module called `random` is pseudo-random, meaning it does not generate +true randomness, but follows an algorithm that simulates randomness. +Since random numbers are generated through a known algorithm, they are not truly random. + +The `random` module is not correctly suited for cryptography and should not be used, +precisely because it is pseudo-random. + +For this reason, in version 3.6, Python introduced the `secrets` module, which generates +cryptographically strong random numbers that provide the greater security required for cryptography. + +Since this is only an exercise, `random` is fine to use, but note that **it would be +very insecure if actually used for cryptography.** ### Submitting Exercises diff --git a/exercises/simple-cipher/.meta/hints.md b/exercises/simple-cipher/.meta/hints.md new file mode 100644 index 0000000000..b2358ef5d3 --- /dev/null +++ b/exercises/simple-cipher/.meta/hints.md @@ -0,0 +1,16 @@ +## Should I use random or secrets? + +Python, as of version 3.6, includes two different random modules. + +The module called `random` is pseudo-random, meaning it does not generate +true randomness, but follows an algorithm that simulates randomness. +Since random numbers are generated through a known algorithm, they are not truly random. + +The `random` module is not correctly suited for cryptography and should not be used, +precisely because it is pseudo-random. + +For this reason, in version 3.6, Python introduced the `secrets` module, which generates +cryptographically strong random numbers that provide the greater security required for cryptography. + +Since this is only an exercise, `random` is fine to use, but note that **it would be +very insecure if actually used for cryptography.** diff --git a/exercises/simple-cipher/README.md b/exercises/simple-cipher/README.md index 83779a968d..d271150fa4 100644 --- a/exercises/simple-cipher/README.md +++ b/exercises/simple-cipher/README.md @@ -83,6 +83,23 @@ on Wikipedia][dh] for one of the first implementations of this scheme. [1]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png [dh]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +## Should I use random or secrets? + +Python, as of version 3.6, includes two different random modules. + +The module called `random` is pseudo-random, meaning it does not generate +true randomness, but follows an algorithm that simulates randomness. +Since random numbers are generated through a known algorithm, they are not truly random. + +The `random` module is not correctly suited for cryptography and should not be used, +precisely because it is pseudo-random. + +For this reason, in version 3.6, Python introduced the `secrets` module, which generates +cryptographically strong random numbers that provide the greater security required for cryptography. + +Since this is only an exercise, `random` is fine to use, but note that **it would be +very insecure if actually used for cryptography.** + ### Submitting Exercises Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/` directory. From 6b7c4444a7181d63317ae4696b71817202bd719a Mon Sep 17 00:00:00 2001 From: Kyle Nied Date: Tue, 24 Oct 2017 17:13:34 -0400 Subject: [PATCH 44/49] Update "TESTS.md" to show that there is a version of "pytest" for both Python2 and Python3 (#506) * Update TESTS.md Provides information on the different types of "pytest" available, as well as switched "py.test" to "pytest", as recommended here (https://github.com/pytest-dev/pytest/issues/1629#issue-161422224). * Fixed a typo. * Small edit. * Update TESTS.md Cleaned it up, addressed what user "N-Parsons" commented on, fixed some errors, and added new information. * Small Change to "PDB" Section. --- docs/TESTS.md | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 53ea9ddf3f..697282a158 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -4,14 +4,38 @@ We recommend you install [pytest](http://pytest.org/latest/) and [pytest-cache](http://pythonhosted.org/pytest-cache/). `pytest` is a testing tool that will give you more flexibility over running your unit tests. +If you want to install `pytest` for Python 2, then use `pip`. + ```bash pip install pytest pytest-cache ``` +If you instead want the version of `pytest` for Python 3, then use `pip3`. + +```bash +pip3 install pytest pytest-cache +``` If you get a `command not found` response from your system, you can find a tutorial on how to install `pip` [here](https://pip.pypa.io/en/stable/installing/). +**Note:** Whichever version of `pytest` you install last will be the default one used whenever `pytest` is executed, regardless of whether you have installed both versions. + +If you want to check what the default version of `pytest` being used is, run the following: + +```bash +pytest --version +``` + +If you have either version of `pytest` installed and you want to specifically run one of the versions, you can run that version by using `python` with the `-m` flag. + +For example, you could run the Python 3 version of pytest like so: + +```bash +$ python3 -m pytest --version +This is pytest version 3.2.3, imported from /usr/local/lib/python3.5/dist-packages/pytest.py +``` + If you choose not to install `pytest`, you can still run tests individually and skip the rest of this tutorial: @@ -29,7 +53,7 @@ an example here), place yourself in the directory where that exercise has been fetched and run: ```bash -py.test bob_test.py +pytest bob_test.py ``` **Note:** To run the tests you need to pass the name of the testsuite file to @@ -56,7 +80,7 @@ The above will run all the tests, whether they fail or not. If you'd rather stop the process and exit on the first failure, run: ```bash -py.test -x bob_test.py +pytest -x bob_test.py ``` ### Failed Tests First @@ -64,14 +88,14 @@ py.test -x bob_test.py `pytest-cache` remembers which tests failed, and can run those tests first. ```bash -py.test --ff bob_test.py +pytest --ff bob_test.py ``` ### Running All Tests for All Exercises ```bash cd exercism/python/ -py.test +pytest ``` ## Recommended Workflow @@ -80,7 +104,7 @@ We recommend you run this command while working on exercises. ```bash cd exercism/python/bob -py.test -x --ff bob_test.py +pytest -x --ff bob_test.py ``` ## PDB @@ -89,28 +113,28 @@ Will drop you into the python debugger when a test fails. To learn how to use pdb, check out the [documentation](https://docs.python.org/3/library/pdb.html#debugger-commands). -You may also be interested in watching [Clayton Parker's "So you think you can -pdb?" PyCon 2015 talk](https://www.youtube.com/watch?v=P0pIW5tJrRM) - ```bash -py.test --pdb bob_test.py +pytest --pdb bob_test.py ``` +You may also be interested in watching [Clayton Parker's "So you think you can +pdb?" PyCon 2015 talk](https://www.youtube.com/watch?v=P0pIW5tJrRM). + ## PEP8 PEP8 is the [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/). If you would like to test for compliance to the style guide, install -[pytest-pep8](https://pypi.python.org/pypi/pytest-pep8) +[pytest-pep8](https://pypi.python.org/pypi/pytest-pep8). ```bash pip install pytest-pep8 ``` -and add the pep8 flag to your command +Then, just add the `--pep8` flag to your command ```bash -py.test --pep8 bob_test.py +pytest --pep8 bob_test.py ``` Read the [pytest documentation](http://pytest.org/latest/contents.html#toc) and From 6ba1846f9ce212970a63b41b4d7f4ef53c3ac6c3 Mon Sep 17 00:00:00 2001 From: Nathan Parsons Date: Wed, 25 Oct 2017 07:53:53 +0100 Subject: [PATCH 45/49] README: Add new conventions and add links to relevant issues/PRs (#977) --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 93e5983408..873c814134 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,12 @@ A list of missing exercise can be found here: http://exercism.io/languages/pytho ### Conventions -- We use minimalistic stub files for all exercises (#272). +- We use minimalistic stub files for all exercises ([#272](https://github.com/exercism/python/issues/272)). - We use `unittest` (Python Standard Library) and no 3rd-party-framework. -- We use the parameter order `self.assertEqual(actual, expected)` (#440). -- We use context managers (`with self.assertRaises(\):`) for testing for exceptions (#477). +- We use the parameter order `self.assertEqual(actual, expected)` ([#440](https://github.com/exercism/python/issues/440)). +- We use context managers (`with self.assertRaises(\):`) for testing for exceptions ([#477](https://github.com/exercism/python/issues/477)). +- We use `assertIs(actual, True)` and `assertIs(actual, False)` rather than `assertTrue(actual)` or `assertFalse(actual)` ([#419](https://github.com/exercism/python/pull/419)). +- We use a comment string in the test file to reference the version of the exercise's `canonical-data.json` that tests were adapted from (wording can be found in: [#784](https://github.com/exercism/python/issues/784)). ### Testing @@ -56,12 +58,12 @@ It will automatically check the code style, the problem configuration, and run t ## Pull Requests -We :heart: pull requests! +We :heart: pull requests! We even :sparkling_heart: them if they contain well written commit messages! Please write the first line of your commit message in the following style: -```exercise-name: Change some things``` +```exercise-name: Change some things``` Please try to follow the [The seven rules of a great Git commit message](https://chris.beams.io/posts/git-commit/#seven-rules) like to capitalize the subject line and use the imperative mood. If there are more details to add, put those into the body of the commit message. From 50d3759c6d99577ee0f167d865ed8f49f3ccc779 Mon Sep 17 00:00:00 2001 From: Nathan Parsons Date: Wed, 25 Oct 2017 08:17:29 +0100 Subject: [PATCH 46/49] forth: Update version strings to reference problem-specifications (#986) --- exercises/forth/forth_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/forth/forth_test.py b/exercises/forth/forth_test.py index f8dac88bf6..53fff3b091 100644 --- a/exercises/forth/forth_test.py +++ b/exercises/forth/forth_test.py @@ -3,7 +3,7 @@ from forth import evaluate, StackUnderflowError -# test cases adapted from `x-common//canonical-data.json` @ version: 1.2.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0 class ForthAdditionTest(unittest.TestCase): From 94da359c132f1dc953f93f5e3e1b9c6c3ace0e2c Mon Sep 17 00:00:00 2001 From: Ilya Khadykin Date: Wed, 25 Oct 2017 11:29:30 +0300 Subject: [PATCH 47/49] Fix topics naming to meet convention to be consistent amoung other tracks (#987) --- config.json | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/config.json b/config.json index 0df3cae7ec..1ef0957810 100644 --- a/config.json +++ b/config.json @@ -9,9 +9,9 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (if-else statements)", - "optional values", - "text formatting" + "conditionals", + "optional_values", + "text_formatting" ] }, { @@ -21,7 +21,7 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (if-else statements)", + "conditionals", "booleans", "logic" ] @@ -46,8 +46,8 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (loops)", - "control-flow (if-else statements)", + "loops", + "conditionals", "strings", "algorithms", "filtering", @@ -61,8 +61,8 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (if-else statements)", - "control-flow (loops)", + "conditionals", + "loops", "maps", "strings", "logic", @@ -76,8 +76,8 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (if-else statements)", - "control-flow (loops)", + "conditionals", + "loops", "sequences", "sets", "strings", @@ -96,8 +96,8 @@ "strings", "algorithms", "logic", - "pattern recognition", - "text formatting" + "pattern_recognition", + "text_formatting" ] }, { @@ -158,7 +158,7 @@ "topics": [ "strings", "logic", - "control-flow (loops)" + "loops" ] }, { @@ -479,8 +479,7 @@ "topics": [ "lists", "searching", - "loops", - "iteration" + "loops" ] }, { @@ -672,7 +671,7 @@ "topics": [ "lists", "parsing", - "transformation", + "transforming", "loops", "games" ] @@ -908,7 +907,7 @@ "time", "mathematics", "logic", - "text formatting" + "text_formatting" ] }, { @@ -919,7 +918,7 @@ "difficulty": 4, "topics": [ "files", - "text formatting", + "text_formatting", "searching" ] }, @@ -967,7 +966,7 @@ "difficulty": 3, "topics": [ "strings", - "pattern matching" + "pattern_matching" ] }, { @@ -978,7 +977,7 @@ "difficulty": 3, "topics": [ "strings", - "pattern matching" + "pattern_matching" ] }, { @@ -988,9 +987,9 @@ "unlocked_by": null, "difficulty": 4, "topics": [ - "Control-flow (loops)", - "Arrays", - "Algorithms" + "loops", + "arrays", + "algorithms" ] }, { @@ -1000,9 +999,9 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (if-else statements)", - "optional values", - "text formatting" + "conditionals", + "optional_values", + "text_formatting" ] }, { @@ -1012,7 +1011,7 @@ "unlocked_by": null, "difficulty": 1, "topics": [ - "control-flow (loops)" + "loops" ] }, { From 834cb964303e77f49523ed1528f99791431498a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guirao=20Rodr=C3=ADguez?= Date: Wed, 25 Oct 2017 14:23:03 +0200 Subject: [PATCH 48/49] sum-of-multiples: Update tests to version 1.1.0 --- exercises/sum-of-multiples/sum_of_multiples_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exercises/sum-of-multiples/sum_of_multiples_test.py b/exercises/sum-of-multiples/sum_of_multiples_test.py index affc13195d..f1e20cfd27 100644 --- a/exercises/sum-of-multiples/sum_of_multiples_test.py +++ b/exercises/sum-of-multiples/sum_of_multiples_test.py @@ -12,7 +12,7 @@ from sum_of_multiples import sum_of_multiples -# test cases adapted from `x-common//canonical-data.json` @ version: 1.0.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 class SumOfMultiplesTest(unittest.TestCase): def test_multiples_of_3_or_5_up_to_1(self): @@ -21,6 +21,9 @@ def test_multiples_of_3_or_5_up_to_1(self): def test_multiples_of_3_or_5_up_to_4(self): self.assertEqual(sum_of_multiples(4, [3, 5]), 3) + def test_multiples_of_3_up_to_7(self): + self.assertEqual(sum_of_multiples(7, [3]), 9) + def test_multiples_of_3_or_5_up_to_10(self): self.assertEqual(sum_of_multiples(10, [3, 5]), 23) From 8b90343deb066df5173e860796f6eb399f3a9ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guirao=20Rodr=C3=ADguez?= Date: Wed, 25 Oct 2017 14:36:29 +0200 Subject: [PATCH 49/49] rotational-cipher: Update tests to version 1.1.0 --- exercises/rotational-cipher/rotational_cipher_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/rotational-cipher/rotational_cipher_test.py b/exercises/rotational-cipher/rotational_cipher_test.py index 82e81a5cfd..88b0983958 100644 --- a/exercises/rotational-cipher/rotational_cipher_test.py +++ b/exercises/rotational-cipher/rotational_cipher_test.py @@ -3,18 +3,18 @@ import rotational_cipher -# test cases adapted from `x-common//canonical-data.json` @ version: 1.0.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 class RotationalCipher(unittest.TestCase): + def test_rotate_a_by_0(self): + self.assertEqual(rotational_cipher.rotate('a', 0), 'a') + def test_rotate_a_by_1(self): self.assertEqual(rotational_cipher.rotate('a', 1), 'b') def test_rotate_a_by_26(self): self.assertEqual(rotational_cipher.rotate('a', 26), 'a') - def test_rotate_a_by_0(self): - self.assertEqual(rotational_cipher.rotate('a', 0), 'a') - def test_rotate_m_by_13(self): self.assertEqual(rotational_cipher.rotate('m', 13), 'z')