From 2c282230d4bd330e84c1dadacee362ba06354238 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 03:01:20 +0000 Subject: [PATCH 01/29] [Sync Iteration] python/collatz-conjecture/1 --- .../1/collatz_conjecture.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 solutions/python/collatz-conjecture/1/collatz_conjecture.py diff --git a/solutions/python/collatz-conjecture/1/collatz_conjecture.py b/solutions/python/collatz-conjecture/1/collatz_conjecture.py new file mode 100644 index 0000000..1b7ce6e --- /dev/null +++ b/solutions/python/collatz-conjecture/1/collatz_conjecture.py @@ -0,0 +1,39 @@ +""" +Collatz Conjecture. + +The rules were deceptively simple. Pick any positive integer. + +If it's even, divide it by 2. +If it's odd, multiply it by 3 and add 1. +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 +""" + + +def steps(number: int) -> int | ValueError: + """ + Return the number of steps it takes to reach 1 according to + the rules of the Collatz Conjecture. + + :param number: any positive integer + :type number: int + :return: number of steps it takes to reach 1 + :rtype: int + """ + if number < 1: + raise ValueError("Only positive integers are allowed") + + n_steps: int = 0 + while number > 1: + # If it's even, divide it by 2 + if number % 2 == 0: + number = number / 2 + # If it's odd, multiply it by 3 and add 1 + else: + number = (number * 3) + 1 + n_steps += 1 + + return n_steps From d86459d3af1000839c96095c293bf177dd714171 Mon Sep 17 00:00:00 2001 From: Egor Kostan` <20955183+ikostan@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:25:51 -0700 Subject: [PATCH 02/29] Create pylint.yml --- .github/workflows/pylint.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pylint.yml diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..c73e032 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') From 7fb88fd23d61e59ffb0cc58594e4c0581d2a26c0 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 20:28:28 -0700 Subject: [PATCH 03/29] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index c73e032..7aebbcf 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.10", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From c5a9f4cecc4acd61b6e6f7eb2f951a9304adffe4 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 21:37:23 -0700 Subject: [PATCH 04/29] LINTING FIXES --- .github/workflows/pylint.yml | 3 +- .pylintrc | 2 + armstrong-numbers/armstrong_numbers_test.py | 61 ++++++ black-jack/black_jack.py | 41 ++-- black-jack/black_jack_test.py | 187 +++++++++--------- card-games/lists.py | 2 +- card-games/lists_test.py | 28 ++- .../list_methods_test.py | 56 ++++-- collatz-conjecture/collatz_conjecture.py | 1 + currency-exchange/exchange.py | 9 +- currency-exchange/exchange_test.py | 3 +- guidos-gorgeous-lasagna/lasagna.py | 4 +- guidos-gorgeous-lasagna/lasagna_test.py | 59 ++++-- hello-world/hello_world_test.py | 5 +- little-sisters-vocab/strings_test.py | 41 ++-- making-the-grade/loops.py | 6 +- making-the-grade/loops_test.py | 35 +++- meltdown-mitigation/conditionals.py | 3 +- meltdown-mitigation/conditionals_test.py | 28 ++- requirements.txt | Bin 202 -> 470 bytes .../python/currency-exchange/1/exchange.py | 12 +- .../guidos-gorgeous-lasagna/1/lasagna.py | 3 +- 22 files changed, 388 insertions(+), 201 deletions(-) create mode 100644 .pylintrc diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 7aebbcf..bf97b7b 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -18,6 +18,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pylint + pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') + python -m pylint --verbose $(Get-ChildItem -Recurse -Filter *.py | Where-Object { $_.FullName -notmatch '\\\.venv\\' -and $_.FullName -notmatch '\\venv\\' } | ForEach-Object { $_.FullName }) --rcfile=.pylintrc \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..fcbba09 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[MASTER] +ignore=.venv diff --git a/armstrong-numbers/armstrong_numbers_test.py b/armstrong-numbers/armstrong_numbers_test.py index 4024766..2deb3d0 100644 --- a/armstrong-numbers/armstrong_numbers_test.py +++ b/armstrong-numbers/armstrong_numbers_test.py @@ -1,3 +1,5 @@ +"""Armstrong Numbers Test Suite.""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json # File last updated on 2023-07-20 @@ -10,29 +12,88 @@ class ArmstrongNumbersTest(unittest.TestCase): + """Armstrong Numbers Test.""" + def test_zero_is_an_armstrong_number(self): + """ + Test that zero is correctly identified as an Armstrong number. + + This test verifies that the function correctly determines that 0 + is an Armstrong number, as 0^1 == 0. + + :returns: None + :rtype: NoneType + """ self.assertIs(is_armstrong_number(0), True) def test_single_digit_numbers_are_armstrong_numbers(self): + """ + Test that all single digit numbers are Armstrong numbers. + + :returns: None. Asserts that a single digit number (e.g., 5) is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(5), True) def test_there_are_no_two_digit_armstrong_numbers(self): + """ + Test that no two-digit numbers are Armstrong numbers. + + :returns: None. Asserts that a two-digit number (e.g., 10) is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(10), False) def test_three_digit_number_that_is_an_armstrong_number(self): + """ + Test that 153 is correctly identified as an Armstrong number. + + :returns: None. Asserts that 153 is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(153), True) def test_three_digit_number_that_is_not_an_armstrong_number(self): + """ + Test that 100 is not identified as an Armstrong number. + + :returns: None. Asserts that 100 is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(100), False) def test_four_digit_number_that_is_an_armstrong_number(self): + """ + Test that 9474 is correctly identified as an Armstrong number. + + :returns: None. Asserts that 9474 is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9474), True) def test_four_digit_number_that_is_not_an_armstrong_number(self): + """ + Test that 9475 is not identified as an Armstrong number. + + :returns: None. Asserts that 9475 is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9475), False) def test_seven_digit_number_that_is_an_armstrong_number(self): + """ + Test that 9926315 is correctly identified as an Armstrong number. + + :returns: None. Asserts that 9926315 is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9926315), True) def test_seven_digit_number_that_is_not_an_armstrong_number(self): + """ + Test that 9926314 is not identified as an Armstrong number. + + :returns: None. Asserts that 9926314 is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9926314), False) diff --git a/black-jack/black_jack.py b/black-jack/black_jack.py index c252b88..fb9dec8 100644 --- a/black-jack/black_jack.py +++ b/black-jack/black_jack.py @@ -19,7 +19,8 @@ def value_of_card(card) -> int: """ if card in 'JKQ': return 10 - elif card == 'A': + + if card == 'A': return 1 return int(card) @@ -29,8 +30,10 @@ def higher_card(card_one, card_two) -> str | tuple[str, str]: """ Determine which card has a higher value in the hand. - :param card_one, card_two: str - cards dealt in hand. See below for values. - :return: str or tuple - resulting Tuple contains both cards if they are of equal value. + :param card_one: str - cards dealt in hand. See below for values. + :param card_two: str - cards dealt in hand. See below for values. + :return: str or tuple - resulting Tuple contains both cards if + they are of equal value. 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 2. 'A' (ace card) = 1 @@ -38,7 +41,8 @@ def higher_card(card_one, card_two) -> str | tuple[str, str]: """ if value_of_card(card_one) == value_of_card(card_two): return card_one, card_two - elif value_of_card(card_one) > value_of_card(card_two): + + if value_of_card(card_one) > value_of_card(card_two): return card_one return card_two @@ -56,11 +60,14 @@ def value_of_ace(card_one, card_two) -> int: 3. '2' - '10' = numerical value. """ total: int = value_of_card(card_one) + value_of_card(card_two) - # Hint: if we already have an ace in hand, then the value for the upcoming ace would be 1. + # Hint: if we already have an ace in hand, then the value for + # the upcoming ace would be 1. if card_one == 'A' or card_two == 'A': return 1 - # The value of the hand with the ace needs to be as high as possible without going over 21. - elif 21 - total >= 11: + # The value of the hand with the ace needs to be as high as + # possible without going over 21. + + if 21 - total >= 11: return 11 return 1 @@ -70,7 +77,10 @@ def is_blackjack(card_one, card_two) -> bool: """ Determine if the hand is a 'natural' or 'blackjack'. - :param card_one, card_two: str - card dealt. See below for values. + :param card_one: card dealt. See below for values. + :type card_one: str + :param card_two: card dealt. See below for values. + :type card_two: str :return: bool - is the hand is a blackjack (two cards worth 21). 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 @@ -81,7 +91,8 @@ def is_blackjack(card_one, card_two) -> bool: # as their first two cards, then the player has a score of 21. if card_one == 'A' and card_two in ('J', 'Q', 'K', '10'): return True - elif card_two == 'A' and card_one in ('J', 'Q', 'K', '10'): + + if card_two == 'A' and card_one in ('J', 'Q', 'K', '10'): return True return False @@ -91,8 +102,10 @@ def can_split_pairs(card_one, card_two) -> bool: """ Determine if a player can split their hand into two hands. - :param card_one, card_two: str - cards dealt. - :return: bool - can the hand be split into two pairs? (i.e. cards are of the same value). + :param card_one: str - cards dealt. + :param card_two: str - cards dealt. + :return: bool - can the hand be split into two pairs? + (i.e. cards are of the same value). """ if value_of_card(card_one) == value_of_card(card_two): return True @@ -104,7 +117,9 @@ def can_double_down(card_one, card_two) -> bool: """ Determine if a blackjack player can place a double down bet. - :param card_one, card_two: str - first and second cards in hand. - :return: bool - can the hand can be doubled down? (i.e. totals 9, 10 or 11 points). + :param card_one: str - first and second cards in hand. + :param card_two: str - first and second cards in hand. + :return: bool - can the hand can be doubled down? + (i.e. totals 9, 10 or 11 points). """ return 9 <= value_of_card(card_one) + value_of_card(card_two) <= 11 diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py index 0962781..89c235d 100644 --- a/black-jack/black_jack_test.py +++ b/black-jack/black_jack_test.py @@ -1,114 +1,111 @@ +from parameterized import parameterized import unittest import pytest from black_jack import ( - value_of_card, - higher_card, - value_of_ace, - is_blackjack, - can_split_pairs, - can_double_down - ) + value_of_card, + higher_card, + value_of_ace, + is_blackjack, + can_split_pairs, + can_double_down +) class BlackJackTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_value_of_card(self): - test_data = [('2', 2), ('5', 5), ('8', 8), - ('A', 1), ('10', 10), ('J', 10), - ('Q', 10), ('K', 10)] - - for variant, (card, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', card=card, expected=expected): - actual_result = value_of_card(card) - error_msg = (f'Called value_of_card({card}). ' - f'The function returned {actual_result} as the value of the {card} card, ' - f'but the test expected {expected} as the {card} card value.') - - self.assertEqual(actual_result, expected, msg=error_msg) - + @parameterized.expand([ + ('2', 2), ('5', 5), ('8', 8), + ('A', 1), ('10', 10), ('J', 10), + ('Q', 10), ('K', 10), + ]) + def test_value_of_card(self, card, expected): + actual_result = value_of_card(card) + error_msg = (f'Called value_of_card({card}). ' + f'The function returned {actual_result} ' + f'as the value of ' + f'the {card} card, ' + f'but the test expected {expected} as the ' + f'{card} card value.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=2) - def test_higher_card(self): - test_data = [('A', 'A', ('A', 'A')), - ('10', 'J', ('10', 'J')), - ('3', 'A', '3'), - ('3', '6', '6'), - ('Q', '10', ('Q', '10')), - ('4', '4', ('4', '4')), - ('9', '10', '10'), - ('6', '9', '9'), - ('4', '8', '8')] - - for variant, (card_one, card_two, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, expected=expected): - actual_result = higher_card(card_one, card_two) - error_msg = (f'Called higher_card({card_one}, {card_two}). ' - f'The function returned {actual_result}, ' - f'but the test expected {expected} as the result for the cards {card_one, card_two}.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + ('A', 'A', ('A', 'A')), + ('10', 'J', ('10', 'J')), + ('3', 'A', '3'), + ('3', '6', '6'), + ('Q', '10', ('Q', '10')), + ('4', '4', ('4', '4')), + ('9', '10', '10'), + ('6', '9', '9'), + ('4', '8', '8'), + ]) + def test_higher_card(self, card_one, card_two, expected): + actual_result = higher_card(card_one, card_two) + error_msg = (f'Called higher_card({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the result for ' + f'the cards {card_one, card_two}.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=3) - def test_value_of_ace(self): - test_data = [('2', '3', 11), ('3', '6', 11), ('5', '2', 11), - ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), - ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), - ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1)] - - for variant, (card_one, card_two, ace_value) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, ace_value=ace_value): - actual_result = value_of_ace(card_one, card_two) - error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' - f'The function returned {actual_result}, ' - f'but the test expected {ace_value} as the value of an ace card ' - f'when the hand includes {card_one, card_two}.') - - self.assertEqual(value_of_ace(card_one, card_two), ace_value, msg=error_msg) + @parameterized.expand([ + ('2', '3', 11), ('3', '6', 11), ('5', '2', 11), + ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), + ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), + ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1), + ]) + def test_value_of_ace(self, card_one, card_two, ace_value): + actual_result = value_of_ace(card_one, card_two) + error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {ace_value} ' + f'as the value of an ace card ' + f'when the hand includes {card_one, card_two}.') + self.assertEqual(actual_result, ace_value, msg=error_msg) @pytest.mark.task(taskno=4) - def test_is_blackjack(self): - test_data = [(('A', 'K'), True), (('10', 'A'), True), - (('10', '9'), False), (('A', 'A'), False), - (('4', '7'), False), (('9', '2'), False), - (('Q', 'K'), False)] - - for variant, (hand, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', hand=hand, expected=expected): - actual_result = is_blackjack(*hand) - error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' - f'The function returned {actual_result}, ' - f'but hand {hand} {"is" if expected else "is not"} a blackjack.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + (('A', 'K'), True), (('10', 'A'), True), + (('10', '9'), False), (('A', 'A'), False), + (('4', '7'), False), (('9', '2'), False), + (('Q', 'K'), False), + ]) + def test_is_blackjack(self, hand, expected): + actual_result = is_blackjack(*hand) + error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"is" if expected else "is not"} ' + f'a blackjack.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=5) - def test_can_split_pairs(self): - test_data = [(('Q', 'K'), True), (('6', '6'), True), - (('A', 'A'), True),(('10', 'A'), False), - (('10', '9'), False)] - - for variant, (hand, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', input=hand, expected=expected): - actual_result = can_split_pairs(*hand) - error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' - f'The function returned {actual_result}, ' - f'but hand {hand} {"can" if expected else "cannot"} be split into pairs.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + (('Q', 'K'), True), (('6', '6'), True), + (('A', 'A'), True), (('10', 'A'), False), + (('10', '9'), False), + ]) + def test_can_split_pairs(self, hand, expected): + actual_result = can_split_pairs(*hand) + error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} ' + f'{"can" if expected else "cannot"} ' + f'be split into pairs.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=6) - def test_can_double_down(self): - test_data = [(('A', '9'), True), (('K', 'A'), True), - (('4', '5'), True),(('A', 'A'), False), - (('10', '2'), False), (('10', '9'), False)] - - for variant, (hand, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', hand=hand, expected=expected): - actual_result = can_double_down(*hand) - error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' - f'The function returned {actual_result}, ' - f'but hand {hand} {"can" if expected else "cannot"} be doubled down.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + (('A', '9'), True), (('K', 'A'), True), + (('4', '5'), True), (('A', 'A'), False), + (('10', '2'), False), (('10', '9'), False), + ]) + def test_can_double_down(self, hand, expected): + actual_result = can_double_down(*hand) + error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"can" if expected else "cannot"} ' + f'be doubled down.') + self.assertEqual(actual_result, expected, msg=error_msg) \ No newline at end of file diff --git a/card-games/lists.py b/card-games/lists.py index 1fc2e68..55f76fc 100644 --- a/card-games/lists.py +++ b/card-games/lists.py @@ -56,7 +56,7 @@ def approx_average_is_average(hand: list[int]) -> bool: :return: bool - does one of the approximate averages equal the `true average`? """ avg: float = card_average(hand) - return (hand[0] + hand[-1]) / 2 == avg or hand[len(hand) // 2] == avg + return avg in ((hand[0] + hand[-1]) / 2, hand[len(hand) // 2]) def average_even_is_average_odd(hand: list[int]) -> bool: diff --git a/card-games/lists_test.py b/card-games/lists_test.py index e550112..0fe4e11 100644 --- a/card-games/lists_test.py +++ b/card-games/lists_test.py @@ -43,8 +43,13 @@ def test_concatenate_rounds(self): [27, 28, 29, 35, 36], [1, 2, 3, 4, 5, 6]] - for variant, ((rounds_1, rounds_2), expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', rounds_1=rounds_1, rounds_2=rounds_2, expected=expected): + for variant, ((rounds_1, rounds_2), expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + rounds_1=rounds_1, + rounds_2=rounds_2, + expected=expected): actual_result = concatenate_rounds(rounds_1, rounds_2) error_message = (f'Called concatenate_rounds({rounds_1}, {rounds_2}). ' f'The function returned {actual_result}, but the tests ' @@ -62,8 +67,13 @@ def test_list_contains_round(self): ([27, 28, 29, 35, 36], 29)] result_data = [False, False, False, True, True, True] - for variant, ((rounds, round_number), expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', rounds=rounds, round_number=round_number, expected=expected): + for variant, ((rounds, round_number), expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + rounds=rounds, + round_number=round_number, + expected=expected): actual_result = list_contains_round(rounds, round_number) error_message = (f'Called list_contains_round({rounds}, {round_number}). ' f'The function returned {actual_result}, but round {round_number} ' @@ -127,11 +137,15 @@ def test_maybe_double_last(self): input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] - for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', hand=list(hand), expected=expected): + for variant, (hand, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + hand=list(hand), + expected=expected): actual_result = maybe_double_last(list(hand)) error_message = (f'Called maybe_double_last({list(hand)}). ' f'The function returned {actual_result}, but ' - f'the tests expected {expected} as the maybe-doubled version of {list(hand)}.') + f'the tests expected {expected} as the ' + f'maybe-doubled version of {list(hand)}.') self.assertEqual(actual_result, expected, msg=error_message) diff --git a/chaitanas-colossal-coaster/list_methods_test.py b/chaitanas-colossal-coaster/list_methods_test.py index 7a754b7..78ff1c8 100644 --- a/chaitanas-colossal-coaster/list_methods_test.py +++ b/chaitanas-colossal-coaster/list_methods_test.py @@ -18,10 +18,14 @@ class ListMethodsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): test_data = [ - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), + ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), + ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), + ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), + ['Drax', 'Nebula', 'Gamora']), ] for variant, (params, expected) in enumerate(test_data, start=1): @@ -29,11 +33,14 @@ def test_add_me_to_the_queue(self): # That mutation wrecks havoc with the verification and error messaging. express_queue, normal_queue, ticket_type, person_name = deepcopy(params) - with self.subTest(f'variation #{variant}', params=params, expected=expected): + with self.subTest(f'variation #{variant}', + params=params, + expected=expected): actual_result = add_me_to_the_queue(*params) error_message = ( - f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'\nCalled add_me_to_the_queue' + f'{express_queue, normal_queue, ticket_type, person_name}.\n' f'The function returned {actual_result},\n' f' but the tests expected {expected} after {person_name} was added.') @@ -42,10 +49,14 @@ def test_add_me_to_the_queue(self): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue_validate_queue(self): test_data = [ - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), + ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), + ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), + ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), + ['Drax', 'Nebula', 'Gamora']), ] for variant, (params, expected) in enumerate(test_data, start=1): @@ -55,14 +66,18 @@ def test_add_me_to_the_queue_validate_queue(self): express, normal, ticket, name = params with self.subTest(f'variation #{variant}', - express=express, normal=normal, - ticket=ticket, name=name, expected=expected): + express=express, + normal=normal, + ticket=ticket, + name=name, + expected=expected): actual_result = add_me_to_the_queue(express, normal, ticket, name) if type == 1: error_message = ( - f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'\nCalled add_me_to_the_queue' + f'{express_queue, normal_queue, ticket_type, person_name}.\n' f'The queue == {express}, but the tests expected\n' f'queue == {expected} after {person_name} was added.' ) @@ -249,9 +264,12 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): test_data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), - (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), - (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], + ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], + ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], + ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') ] for variant, (queue, modified, expected) in enumerate(test_data, start=1): with self.subTest(f'variation #{variant}', queue=queue, modified=modified, expected=expected): @@ -264,9 +282,11 @@ def test_remove_the_last_person(self): expected_queue = modified error_message = (f'\nCalled remove_the_last_person({unmodified_queue}).\n' - f'The function was expected to remove and return the name "{expected_result}" ' + f'The function was expected to remove and return the name ' + f'"{expected_result}" ' f'and change the queue to {expected_queue},\n' - f'but the name "{actual_result}" was returned and the queue == {queue}.') + f'but the name "{actual_result}" was returned and the queue == ' + f'{queue}.') self.assertEqual((actual_result, queue), (expected_result, expected_queue), msg=error_message) diff --git a/collatz-conjecture/collatz_conjecture.py b/collatz-conjecture/collatz_conjecture.py index 7755bfc..a97aab0 100644 --- a/collatz-conjecture/collatz_conjecture.py +++ b/collatz-conjecture/collatz_conjecture.py @@ -13,6 +13,7 @@ """ +# pylint: disable=R0801 def steps(number: int) -> int: """ Return the number of steps it takes to reach 1 according to diff --git a/currency-exchange/exchange.py b/currency-exchange/exchange.py index e1871e5..ee23d07 100644 --- a/currency-exchange/exchange.py +++ b/currency-exchange/exchange.py @@ -1,9 +1,11 @@ """ Functions for calculating steps in exchanging currency. -Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +Python numbers documentation: +https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex -Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +Overview of exchanging currency when travelling: +https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ """ @@ -31,7 +33,7 @@ def get_change(budget: float, :param exchanging_value: float - amount of your money you want to exchange now. :return: float - amount left of your starting currency after exchanging. """ - return budget - exchanging_value + return budget - exchanging_value # pylint: disable=R0801 def get_value_of_bills(denomination: float, @@ -75,6 +77,7 @@ def get_leftover_of_bills(amount: float, return amount % denomination +# pylint: disable=R0801 def exchangeable_value(budget: float, exchange_rate: float, spread: int, diff --git a/currency-exchange/exchange_test.py b/currency-exchange/exchange_test.py index fd3754c..773b6fb 100644 --- a/currency-exchange/exchange_test.py +++ b/currency-exchange/exchange_test.py @@ -133,7 +133,8 @@ def test_exchangeable_value(self): expected=expected): actual_result = exchangeable_value(budget, exchange_rate, spread, denomination) - error_message = (f'Called exchangeable_value{budget, exchange_rate, spread, denomination}. ' + error_message = (f'Called exchangeable_value' + f'{budget, exchange_rate, spread, denomination}. ' f'The function returned {actual_result}, but ' f'The tests expected {expected} as the maximum ' f'value of the new currency .') diff --git a/guidos-gorgeous-lasagna/lasagna.py b/guidos-gorgeous-lasagna/lasagna.py index 595a253..577645a 100644 --- a/guidos-gorgeous-lasagna/lasagna.py +++ b/guidos-gorgeous-lasagna/lasagna.py @@ -13,6 +13,7 @@ PREPARATION_TIME: int = 2 +# pylint: disable=R0801 def bake_time_remaining(elapsed_bake_time: int) -> int: """ Calculate the bake time remaining. @@ -54,7 +55,8 @@ def elapsed_time_in_minutes(number_of_layers: int, :param number_of_layers: The number of layers added to the lasagna. :type number_of_layers: int - :param elapsed_bake_time: The number of minutes the lasagna has spent baking in the oven already. + :param elapsed_bake_time: The number of minutes the lasagna has + spent baking in the oven already. :type elapsed_bake_time: int :return: Elapsed time in minutes. :rtype: int diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index 4066aa8..f7b39ab 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -1,9 +1,11 @@ import unittest import pytest -# For this first exercise, it is really important to be clear about how we are importing names for tests. -# To that end, we are putting a try/catch around imports and throwing specific messages to help students -# decode that they need to create and title their constants and functions in a specific way. +# For this first exercise, it is really important to be clear about +# how we are importing names for tests.To that end, we are putting a +# try/catch around imports and throwing specific messages to help +# students decode that they need to create and title their constants +# and functions in a specific way. try: from lasagna import (EXPECTED_BAKE_TIME, bake_time_remaining, @@ -17,13 +19,17 @@ if 'EXPECTED_BAKE_TIME' in item_name: # pylint: disable=raise-missing-from - raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find or import the constant {item_name} in your' - " 'lasagna.py' file.\nDid you misname or forget to define it?") from None - else: - item_name = item_name[:-1] + "()'" - # pylint: disable=raise-missing-from - raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' file, we can not find or import the" - f' function named {item_name}. \nDid you misname or forget to define it?') from None + raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find ' + f'or import the constant {item_name} in your' + " 'lasagna.py' file.\nDid you misname or forget " + "to define it?") from None + + item_name = item_name[:-1] + "()'" + # pylint: disable=raise-missing-from + raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' " + "file, we can not find or import the" + f' function named {item_name}. \nDid you misname ' + f'or forget to define it?') from None # Here begins the formal test cases for the exercise. @@ -39,11 +45,16 @@ def test_bake_time_remaining(self): input_data = [1, 2, 5, 10, 15, 23, 33, 39] result_data = [39, 38, 35, 30, 25, 17, 7, 1] - for variant, (time, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', time=time, expected=expected): + for variant, (time, expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + time=time, + expected=expected): actual_result = bake_time_remaining(time) - failure_msg = (f'Called bake_time_remaining({time}). ' - f'The function returned {actual_result}, but the tests ' + failure_msg = (f'Called bake_time_remaining({time}). ' + f'The function returned {actual_result}, ' + f'but the tests ' f'expected {expected} as the remaining bake time.') self.assertEqual(actual_result, expected, msg=failure_msg) @@ -53,11 +64,16 @@ def test_preparation_time_in_minutes(self): input_data = [1, 2, 5, 8, 11, 15] result_data = [2, 4, 10, 16, 22, 30] - for variant, (layers, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', layers=layers, expected=expected): + for variant, (layers, expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + layers=layers, + expected=expected): actual_result = preparation_time_in_minutes(layers) failure_msg = (f'Called preparation_time_in_minutes({layers}). ' - f'The function returned {actual_result}, but the tests ' + f'The function returned {actual_result}, ' + f'but the tests ' f'expected {expected} as the preparation time.') self.assertEqual(actual_result, expected, msg=failure_msg) @@ -68,8 +84,13 @@ def test_elapsed_time_in_minutes(self): time_data = (3, 7, 8, 4, 15, 20) result_data = [5, 11, 18, 20, 37, 50] - for variant, (layers, time, expected) in enumerate(zip(layer_data, time_data, result_data), start=1): - with self.subTest(f'variation #{variant}', layers=layers, time=time, expected=expected): + for variant, (layers, time, expected) in enumerate( + zip(layer_data, time_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + layers=layers, + time=time, + expected=expected): actual_result = elapsed_time_in_minutes(layers, time) failure_msg = (f'Called elapsed_time_in_minutes({layers}, {time}). ' f'The function returned {actual_result}, but the tests ' diff --git a/hello-world/hello_world_test.py b/hello-world/hello_world_test.py index c2d1154..5617b64 100644 --- a/hello-world/hello_world_test.py +++ b/hello-world/hello_world_test.py @@ -26,6 +26,7 @@ class HelloWorldTest(unittest.TestCase): def test_say_hi(self): - msg = ("\n\nThis test expects a return of the string 'Hello, World!' \nDid you use print('Hello, World!') by " - "mistake?") + msg = ("\n\nThis test expects a return of the string " + "'Hello, World!' \nDid you use print('Hello, World!') " + "by mistake?") self.assertEqual(hello(), "Hello, World!", msg=msg) diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py index b13d4e9..0c670df 100644 --- a/little-sisters-vocab/strings_test.py +++ b/little-sisters-vocab/strings_test.py @@ -15,19 +15,21 @@ def test_add_prefix_un(self): for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): with self.subTest(f'variation #{variant}', word=word, expected=expected): - actual_result = add_prefix_un(word) error_message = (f'Called add_prefix_un("{word}"). ' - f'The function returned "{actual_result}", but the ' - f'tests expected "{expected}" after adding "un" as a prefix.') + f'The function returned "{actual_result}", but the ' + f'tests expected "{expected}" after adding "un" as a prefix.') self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_make_word_groups_en(self): - input_data = ['en', 'circle', 'fold', 'close', 'joy', 'lighten', 'tangle', 'able', 'code', 'culture'] - expected = ('en :: encircle :: enfold :: enclose :: enjoy :: enlighten ::' - ' entangle :: enable :: encode :: enculture') + input_data = ['en', 'circle', 'fold', 'close', + 'joy', 'lighten', 'tangle', 'able', + 'code', 'culture'] + expected = ('en :: encircle :: enfold :: enclose :: ' + 'enjoy :: enlighten ::' + ' entangle :: enable :: encode :: enculture') actual_result = make_word_groups(input_data) error_message = (f'Called make_word_groups({input_data}). ' @@ -87,11 +89,17 @@ def test_make_words_groups_inter(self): @pytest.mark.task(taskno=3) def test_remove_suffix_ness(self): - input_data = ['heaviness', 'sadness', 'softness', 'crabbiness', 'lightness', 'artiness', 'edginess'] - result_data = ['heavy', 'sad', 'soft', 'crabby', 'light', 'arty', 'edgy'] - - for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', word=word, expected=expected): + input_data = ['heaviness', 'sadness', 'softness', + 'crabbiness', 'lightness', 'artiness', + 'edginess'] + result_data = ['heavy', 'sad', 'soft', 'crabby', + 'light', 'arty', 'edgy'] + + for variant, (word, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + word=word, + expected=expected): actual_result = remove_suffix_ness(word) error_message = (f'Called remove_suffix_ness("{word}"). ' f'The function returned "{actual_result}", ' @@ -115,11 +123,16 @@ def test_adjective_to_verb(self): result_data = ['brighten', 'darken', 'harden', 'soften', 'lighten', 'dampen', 'shorten', 'weaken', 'blacken'] - for variant, (sentence, index, expected) in enumerate(zip(input_data, index_data, result_data), start=1): - with self.subTest(f'variation #{variant}', sentence=sentence, index=index, expected=expected): + for variant, (sentence, index, expected) in enumerate( + zip(input_data, index_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + sentence=sentence, + index=index, + expected=expected): actual_result = adjective_to_verb(sentence, index) error_message = (f'Called adjective_to_verb("{sentence}", {index}). ' - f'The function returned "{actual_result}", but the tests ' + f'The function returned "{actual_result}", ' + f'but the tests ' f'expected "{expected}" as the verb for ' f'the word at index {index}.') diff --git a/making-the-grade/loops.py b/making-the-grade/loops.py index afe3994..06668b7 100644 --- a/making-the-grade/loops.py +++ b/making-the-grade/loops.py @@ -18,7 +18,7 @@ def count_failed_students(student_scores: list) -> int: :param student_scores: list - containing int student scores. :return: int - count of student scores at or below 40. """ - return len([score for score in student_scores if score <= 40.0]) + return len([score for score in student_scores if score <= 40.0]) # pylint: disable=R0801 def above_threshold(student_scores: list, @@ -33,7 +33,7 @@ def above_threshold(student_scores: list, :param threshold: int - threshold to cross to be the "best" score. :return: list - of integer scores that are at or above the "best" threshold. """ - return [score for score in student_scores if score >= threshold] + return [score for score in student_scores if score >= threshold] # pylint: disable=R0801 def letter_grades(highest: int) -> list: @@ -54,6 +54,7 @@ def letter_grades(highest: int) -> list: return [41 + i * interval for i in range(4)] +# pylint: disable=R0801 def student_ranking(student_scores: list, student_names: list) -> list[str]: """ @@ -67,6 +68,7 @@ def student_ranking(student_scores: list, range(1, len(student_scores) + 1), student_scores, student_names)] +# pylint: disable=R0801 def perfect_score(student_info: list) -> list: """ Create a list that contains the name and grade of the first diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py index 598e2b0..f34b0dd 100644 --- a/making-the-grade/loops_test.py +++ b/making-the-grade/loops_test.py @@ -29,18 +29,26 @@ def test_round_scores(self): [90, 40, 55, 70, 31, 25, 80, 95, 39, 40], [50, 36, 77, 41, 43, 78, 64, 91, 29, 88]] - for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): - with self.subTest(f'variation #{variant}', student_scores=student_scores, expected=expected): + for variant, (student_scores, expected) in enumerate( + zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + student_scores=student_scores, + expected=expected): - # Because the test_input is a tuple, it has to be converted to a list for the function call. + # Because the test_input is a tuple, it has to be converted + # to a list for the function call. actual_result = round_scores(list(student_scores)) error_message = (f'Called round_scores({list(student_scores)}). ' - f'The function returned {sorted(actual_result)} after sorting, but ' + f'The function returned {sorted(actual_result)} ' + f'after sorting, but ' f'the tests expected {sorted(expected)} after sorting. ' f'One or more scores were rounded incorrectly.') # everything is sorted for easier comparison. - self.assertEqual(sorted(actual_result), sorted(expected), msg=error_message) + self.assertEqual( + sorted(actual_result), + sorted(expected), + msg=error_message) @pytest.mark.task(taskno=2) def test_count_failed_students(self): @@ -48,7 +56,8 @@ def test_count_failed_students(self): [40, 40, 35, 70, 30, 41, 90]] result_data = [0,4] - for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): + for variant, (student_scores, expected) in enumerate( + zip(test_data, result_data), start=1): with self.subTest(f'variation #{variant}', student_scores=student_scores, expected=expected): @@ -95,8 +104,11 @@ def test_letter_grades(self): [41, 54, 67, 80], [41, 51, 61, 71]] - for variant, (highest, expected) in enumerate(zip(test_data, result_data), start=1): - with self.subTest(f'variation #{variant}', highest=highest, expected=expected): + for variant, (highest, expected) in enumerate( + zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + highest=highest, + expected=expected): actual_result = letter_grades(highest) error_message = (f'Called letter_grades({highest}). ' f'The function returned {actual_result}, but ' @@ -143,9 +155,12 @@ def test_perfect_score(self): result_data = [['Joci', 100],[], [], [], ['Raiana', 100]] - for variant, (student_info, expected) in enumerate(zip(test_data, result_data), start=1): + for variant, (student_info, expected) in enumerate( + zip(test_data, result_data), start=1): - with self.subTest(f'variation #{variant}', student_info=student_info, expected=expected): + with self.subTest(f'variation #{variant}', + student_info=student_info, + expected=expected): actual_result = perfect_score(student_info) error_message = (f'Called perfect_score({student_info}). ' f'The function returned {actual_result}, but ' diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index 87b831b..f6b7af4 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -65,7 +65,8 @@ def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: if thr_percent - 10 <= current_state <= thr_percent + 10: return 'NORMAL' - elif current_state < thr_percent - 10: + + if current_state < thr_percent - 10: return 'LOW' return 'DANGER' diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py index 5e48ca3..6e9ab56 100644 --- a/meltdown-mitigation/conditionals_test.py +++ b/meltdown-mitigation/conditionals_test.py @@ -26,13 +26,18 @@ def test_is_criticality_balanced(self): for variant, data in enumerate(test_data, start=1): temp, neutrons_emitted, expected = data - with self.subTest(f'variation #{variant}', temp=temp, neutrons_emitted=neutrons_emitted, expected=expected): + with self.subTest(f'variation #{variant}', + temp=temp, + neutrons_emitted=neutrons_emitted, + expected=expected): # pylint: disable=assignment-from-no-return actual_result = is_criticality_balanced(temp, neutrons_emitted) - failure_message = (f'Called is_criticality_balanced({temp}, {neutrons_emitted}). ' + failure_message = (f'Called is_criticality_balanced(' + f'{temp}, {neutrons_emitted}). ' f' The function returned {actual_result}, ' - f'but the test expected {expected} as the return value.') + f'but the test expected {expected} as the ' + f'return value.') self.assertEqual(actual_result, expected, failure_message) @@ -49,12 +54,16 @@ def test_reactor_efficiency(self): for variant, data in enumerate(test_data, start=1): current, expected = data - with self.subTest(f'variation #{variant}', voltage=voltage, current=current, - theoretical_max_power=theoretical_max_power, expected=expected): + with self.subTest(f'variation #{variant}', + voltage=voltage, + current=current, + theoretical_max_power=theoretical_max_power, + expected=expected): # pylint: disable=assignment-from-no-return actual_result = reactor_efficiency(voltage, current, theoretical_max_power) - failure_message =(f'Called reactor_efficiency({voltage}, {current}, {theoretical_max_power}). ' + failure_message =(f'Called reactor_efficiency(' + f'{voltage}, {current}, {theoretical_max_power}). ' f'The function returned {actual_result}, ' f'but the test expected {expected} as the return value.') @@ -70,12 +79,15 @@ def test_fail_safe(self): (400, 'LOW'), (1101, 'DANGER'), (1200, 'DANGER')) for variant, (neutrons_per_second, expected) in enumerate(test_data, start=1): - with self.subTest(f'variation #{variant}', temp=temp, neutrons_per_second=neutrons_per_second, + with self.subTest(f'variation #{variant}', + temp=temp, + neutrons_per_second=neutrons_per_second, threshold=threshold, expected=expected): # pylint: disable=assignment-from-no-return actual_result = fail_safe(temp, neutrons_per_second, threshold) - failure_message = (f'Called fail_safe({temp}, {neutrons_per_second}, {threshold}). ' + failure_message = (f'Called fail_safe(' + f'{temp}, {neutrons_per_second}, {threshold}). ' f'The function returned {actual_result}, ' f'but the test expected {expected} as the return value.') diff --git a/requirements.txt b/requirements.txt index 015de27c9da29e7b8a25ce6bff042d73151ebfaa..ad58882afce5f0a1f504e0fdc86c550ca687c885 100644 GIT binary patch literal 470 zcmY+BQEtL85Jcy@Qm;}GXn}t84qSnTG>S-4M9fEe__lA>Nd&SsjyyZFGxp!_QG=4M z`c|WpK3E&QL=`IZtS6OVEp^pV9p#=CJ+IU@I!<(_p%uiK|`fg!fKuVqY)@b*O`rAWC_Fa*(B15Ae_MFry-tT%_loCFj zwWFtDA7ZYv`d;?o>O$5V(`t=@I!pF#&j(Ix_!W-Q#LtO6YdtB9XxMX3J>U7PKZaYs ajPG<0&kpjPB>Z)-&+s$u9L4l*cJL1!dqhJ3 delta 24 gcmcb{e2S6j|G$YUN)r=QChk+2Y{BR float: """ - Return only the total value of the bills (excluding fractional amounts) the booth would give back. + Return only the total value of the bills (excluding fractional amounts) + the booth would give back. The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder. @@ -79,7 +82,8 @@ def exchangeable_value(budget: float, spread: int, denomination: int) -> int: """ - Return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*. + Return the maximum value of the new currency after calculating + the *exchange rate* plus the *spread*. :param budget: float - the amount of your money you are planning to exchange. :param exchange_rate: float - the unit value of the foreign currency. diff --git a/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py b/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py index 595a253..a69c70b 100644 --- a/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py +++ b/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py @@ -54,7 +54,8 @@ def elapsed_time_in_minutes(number_of_layers: int, :param number_of_layers: The number of layers added to the lasagna. :type number_of_layers: int - :param elapsed_bake_time: The number of minutes the lasagna has spent baking in the oven already. + :param elapsed_bake_time: The number of minutes the lasagna has spent + baking in the oven already. :type elapsed_bake_time: int :return: Elapsed time in minutes. :rtype: int From 23f6c6c27bd3df56eb345681ed82cb120d7140d9 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 21:40:26 -0700 Subject: [PATCH 05/29] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index bf97b7b..9d883d9 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - python -m pylint --verbose $(Get-ChildItem -Recurse -Filter *.py | Where-Object { $_.FullName -notmatch '\\\.venv\\' -and $_.FullName -notmatch '\\venv\\' } | ForEach-Object { $_.FullName }) --rcfile=.pylintrc \ No newline at end of file + python -m pylint --verbose $(find . -name "*.py" ! -path "*/.venv/*" ! -path "*/venv/*") --rcfile=.pylintrc \ No newline at end of file From df47e9e7bf6d16ca1e25c355c39b7b7d954636ad Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:03:42 -0700 Subject: [PATCH 06/29] Docstring added --- armstrong-numbers/armstrong_numbers_test.py | 42 ++++++++++- black-jack/black_jack_test.py | 77 ++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/armstrong-numbers/armstrong_numbers_test.py b/armstrong-numbers/armstrong_numbers_test.py index 2deb3d0..6a9d54d 100644 --- a/armstrong-numbers/armstrong_numbers_test.py +++ b/armstrong-numbers/armstrong_numbers_test.py @@ -1,4 +1,44 @@ -"""Armstrong Numbers Test Suite.""" +""" +Armstrong Numbers Test Suite Documentation + +## Overview + +This test suite validates the `is_armstrong_number` function, +ensuring its correct behavior for various types of numbers: + +- Single-digit and multi-digit numbers +- Known Armstrong numbers and non-Armstrong numbers + +Tests are auto-generated based on canonical data from +[Exercism problem specifications](https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json). + +## Structure + +- **Framework:** Uses Python's built-in `unittest`. +- **Target Function:** `is_armstrong_number` (imported from `armstrong_numbers` module). + +## Test Cases + +| Test Description | Input | Expected Output | +|---------------------------------------------------------|----------|-----------------| +| Zero is an Armstrong number | 0 | `True` | +| Single-digit numbers are Armstrong numbers | 5 | `True` | +| No two-digit numbers (e.g. 10) are Armstrong numbers | 10 | `False` | +| 153 is an Armstrong number | 153 | `True` | +| 100 is not an Armstrong number | 100 | `False` | +| 9474 is an Armstrong number | 9474 | `True` | +| 9475 is not an Armstrong number | 9475 | `False` | +| 9926315 is an Armstrong number | 9926315 | `True` | +| 9926314 is not an Armstrong number | 9926314 | `False` | + +## Usage + +To run the tests, ensure `is_armstrong_number` is implemented and run: + +```bash +python -m unittest armstrong_numbers_test.py + +""" # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py index 89c235d..9bd10af 100644 --- a/black-jack/black_jack_test.py +++ b/black-jack/black_jack_test.py @@ -1,3 +1,18 @@ +""" +Unit tests suite for blackjack game logic functions. + +This test case class covers the following functionalities: + + - :func:`value_of_card`: Evaluates the value of individual blackjack cards. + - :func:`higher_card`: Determines the higher value card or equality. + - :func:`value_of_ace`: Calculates optimal ace value given two cards. + - :func:`is_blackjack`: Checks if a hand is a 'blackjack' (natural 21). + - :func:`can_split_pairs`: Determines if a hand can be split into two pairs. + - :func:`can_double_down`: Assesses eligibility to double down. + +Tests use parameterized cases with informative error messages for each assertion. +""" + from parameterized import parameterized import unittest import pytest @@ -13,6 +28,19 @@ class BlackJackTest(unittest.TestCase): + """ + Unit test suite for blackjack game logic functions. + + Each test validates a specific utility function from the blackjack module: + - :func:`test_value_of_card`: Checks the correct card value assignment. + - :func:`test_higher_card`: Ensures the function returns the card with higher value or both if equal. + - :func:`test_value_of_ace`: Verifies optimal ace value calculation based on the current hand. + - :func:`test_is_blackjack`: Determines if a two-card hand is a blackjack. + - :func:`test_can_split_pairs`: Tests if two cards can be split into two separate hands. + - :func:`test_can_double_down`: Evaluates if the hand is eligible for doubling down. + + Tests are parameterized for thorough coverage and use descriptive assertion error messages. + """ @pytest.mark.task(taskno=1) @parameterized.expand([ @@ -21,6 +49,15 @@ class BlackJackTest(unittest.TestCase): ('Q', 10), ('K', 10), ]) def test_value_of_card(self, card, expected): + """ + Test that the value_of_card function returns the correct + value for a given card. + + :param card: Card to evaluate. + :type card: str + :param expected: Expected value for the provided card. + :type expected: int + """ actual_result = value_of_card(card) error_msg = (f'Called value_of_card({card}). ' f'The function returned {actual_result} ' @@ -43,6 +80,14 @@ def test_value_of_card(self, card, expected): ('4', '8', '8'), ]) def test_higher_card(self, card_one, card_two, expected): + """ + Test that the higher_card function correctly determines + which card has the higher value. + + :param card_one: First card to compare. + :param card_two: Second card to compare. + :param expected: The expected result for the higher card. + """ actual_result = higher_card(card_one, card_two) error_msg = (f'Called higher_card({card_one}, {card_two}). ' f'The function returned {actual_result}, ' @@ -58,6 +103,14 @@ def test_higher_card(self, card_one, card_two, expected): ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1), ]) def test_value_of_ace(self, card_one, card_two, ace_value): + """ + Test that the value_of_ace function returns the correct ace value + given the other two cards. + + :param card_one: The first card in hand. + :param card_two: The second card in hand. + :param ace_value: The expected ace value. + """ actual_result = value_of_ace(card_one, card_two) error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' f'The function returned {actual_result}, ' @@ -74,6 +127,12 @@ def test_value_of_ace(self, card_one, card_two, ace_value): (('Q', 'K'), False), ]) def test_is_blackjack(self, hand, expected): + """ + Test if a given hand qualifies as blackjack. + + :param hand: List representing the player's hand. + :param expected: Expected boolean indicating if the hand is a blackjack. + """ actual_result = is_blackjack(*hand) error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' f'The function returned {actual_result}, ' @@ -88,6 +147,13 @@ def test_is_blackjack(self, hand, expected): (('10', '9'), False), ]) def test_can_split_pairs(self, hand, expected): + """ + Test whether the `can_split_pairs` function correctly + determines if a given blackjack hand can be split into pairs. + + :param hand: List representing the player's hand. + :param expected: Expected boolean result indicating if split is allowed. + """ actual_result = can_split_pairs(*hand) error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' f'The function returned {actual_result}, ' @@ -103,9 +169,18 @@ def test_can_split_pairs(self, hand, expected): (('10', '2'), False), (('10', '9'), False), ]) def test_can_double_down(self, hand, expected): + """ + Test whether a hand qualifies to double down in blackjack. + + :param hand: The current cards in the player's hand. + :type hand: list + :param expected: The expected boolean result of whether the + hand can double down. + :type expected: bool + """ actual_result = can_double_down(*hand) error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' f'The function returned {actual_result}, ' f'but hand {hand} {"can" if expected else "cannot"} ' f'be doubled down.') - self.assertEqual(actual_result, expected, msg=error_msg) \ No newline at end of file + self.assertEqual(actual_result, expected, msg=error_msg) From 8632e0e30b6c87475a56274fe01c9cddc986bb5b Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:22:39 -0700 Subject: [PATCH 07/29] Docstrings added --- armstrong-numbers/armstrong_numbers_test.py | 1 + black-jack/black_jack_test.py | 4 +- card-games/lists_test.py | 54 ++++++++++- .../list_methods_test.py | 90 +++++++++++++++++++ guidos-gorgeous-lasagna/lasagna_test.py | 1 + meltdown-mitigation/conditionals.py | 1 + 6 files changed, 148 insertions(+), 3 deletions(-) diff --git a/armstrong-numbers/armstrong_numbers_test.py b/armstrong-numbers/armstrong_numbers_test.py index 6a9d54d..092f75d 100644 --- a/armstrong-numbers/armstrong_numbers_test.py +++ b/armstrong-numbers/armstrong_numbers_test.py @@ -1,3 +1,4 @@ +# pylint: disable=C0301 """ Armstrong Numbers Test Suite Documentation diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py index 9bd10af..f01f2df 100644 --- a/black-jack/black_jack_test.py +++ b/black-jack/black_jack_test.py @@ -1,3 +1,4 @@ +# pylint: disable=C0301 """ Unit tests suite for blackjack game logic functions. @@ -13,8 +14,8 @@ Tests use parameterized cases with informative error messages for each assertion. """ -from parameterized import parameterized import unittest +from parameterized import parameterized import pytest from black_jack import ( @@ -27,6 +28,7 @@ ) +# pylint: disable=C0301 class BlackJackTest(unittest.TestCase): """ Unit test suite for blackjack game logic functions. diff --git a/card-games/lists_test.py b/card-games/lists_test.py index 0fe4e11..1c5386e 100644 --- a/card-games/lists_test.py +++ b/card-games/lists_test.py @@ -1,3 +1,14 @@ +# pylint: disable=C0301 +""" +Unit tests for the card game utility functions in the lists module. + +Tests cover core behaviors such as generating rounds, concatenating rounds, +verifying round presence, calculating averages, comparing approximate and +actual averages, comparing even/odd index averages, and conditionally +doubling the last card value. + +Uses unittest framework with pytest marking for grading tasks. +""" import unittest import pytest @@ -13,17 +24,26 @@ class CardGamesTest(unittest.TestCase): + """Unit tests for card games utility functions.""" @pytest.mark.task(taskno=1) def test_get_rounds(self): + """ + Test the get_rounds function with various inputs to ensure it returns + the current round and the next two rounds. + :param self: The test case instance. + """ input_data = [0, 1, 10, 27, 99, 666] result_data = [[0, 1, 2], [1, 2, 3], [10, 11, 12], [27, 28, 29], [99, 100, 101], [666, 667, 668]] - for variant, (number, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', number=number, expected=expected): + for variant, (number, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + number=number, + expected=expected): actual_result = get_rounds(number) error_message = (f'Called get_rounds({number}). ' f'The function returned {actual_result}, ' @@ -34,7 +54,12 @@ def test_get_rounds(self): @pytest.mark.task(taskno=2) def test_concatenate_rounds(self): + """ + Test the concatenate_rounds function to ensure it correctly combines + two lists of rounds. + :param self: The test case instance. + """ input_data = [([], []), ([0, 1], []), ([], [1, 2]), ([1], [2]), ([27, 28, 29], [35, 36]), ([1, 2, 3], [4, 5, 6])] @@ -60,7 +85,12 @@ def test_concatenate_rounds(self): @pytest.mark.task(taskno=3) def test_list_contains_round(self): + """ + Test the list_contains_round function to check if a specific round + is present in the list of played rounds. + :param self: The test case instance. + """ input_data = [([], 1), ([1, 2, 3], 0), ([27, 28, 29, 35, 36], 30), ([1], 1), ([1, 2, 3], 1), @@ -83,7 +113,12 @@ def test_list_contains_round(self): @pytest.mark.task(taskno=4) def test_card_average(self): + """ + Test the card_average function to ensure it calculates the correct + average of card values in a hand. + :param self: The test case instance. + """ input_data = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]] result_data = [1.0, 6.0, 2.5, 37.0] @@ -98,7 +133,12 @@ def test_card_average(self): @pytest.mark.task(taskno=5) def test_approx_average_is_average(self): + """ + Test the approx_average_is_average function to check if approximate + averages match the actual average. + :param self: The test case instance. + """ input_data = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9], [2, 3, 4, 7, 8], [1, 2, 3], [2, 3, 4], [2, 3, 4, 8, 8], [1, 2, 4, 5, 8]] @@ -117,7 +157,12 @@ def test_approx_average_is_average(self): @pytest.mark.task(taskno=6) def test_average_even_is_average_odd(self): + """ + Test the average_even_is_average_odd function to verify if averages + of even and odd indexed cards are equal. + :param self: The test case instance. + """ input_data = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]] result_data = [False, False, True, True, True] @@ -133,7 +178,12 @@ def test_average_even_is_average_odd(self): @pytest.mark.task(taskno=7) def test_maybe_double_last(self): + """ + Test the maybe_double_last function to ensure it doubles the last card + if it is a Jack (11). + :param self: The test case instance. + """ input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] diff --git a/chaitanas-colossal-coaster/list_methods_test.py b/chaitanas-colossal-coaster/list_methods_test.py index 78ff1c8..2d68b0b 100644 --- a/chaitanas-colossal-coaster/list_methods_test.py +++ b/chaitanas-colossal-coaster/list_methods_test.py @@ -1,3 +1,20 @@ +# pylint: disable=C0301 +""" +Unit tests for queue management functions at Chaitana's roller coaster. + +This test suite, +- Validates correct functional behavior for basic queue operations (add, remove, insert, search, and sort) for both express and normal queues. +- Ensures that each function both returns the correct result and properly mutates or does not mutate the input lists as expected. +- Employs parameterized testing patterns, subTest blocks, and deep copies to safeguard against unintended mutation of shared state, ensuring accurate and isolated assertions. +- Uses unittest and pytest frameworks for flexible test discovery and marking (with @pytest.mark.task to delineate test responsibilities). + +Each function imported from `list_methods` is tested with a range of scenarios, including: +- Standard and edge inputs, +- In-place versus return-value mutation expectations, +- Validation that the queue's structure and contents match anticipated post-conditions. + +Users can run this file as a standard unittest/pytest module. Extend or edit test cases as new queue behaviors are implemented. +""" import unittest from copy import deepcopy import pytest @@ -14,9 +31,22 @@ ) +# pylint: disable=C0301 class ListMethodsTest(unittest.TestCase): + """ + Unit test suite for list manipulation functions. + + Tests various utility methods defined in the list_methods module. + """ + @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): + """ + Test the add_me_to_the_queue function to ensure it adds a person + to the correct queue based on ticket type and returns the updated queue. + + :param self: The test case instance. + """ test_data = [ ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), @@ -48,6 +78,12 @@ def test_add_me_to_the_queue(self): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue_validate_queue(self): + """ + Test the add_me_to_the_queue function to validate that it mutates + and returns the correct queue based on ticket type. + + :param self: The test case instance. + """ test_data = [ ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), @@ -95,6 +131,12 @@ def test_add_me_to_the_queue_validate_queue(self): @pytest.mark.task(taskno=2) def test_find_my_friend(self): + """ + Test the find_my_friend function to ensure it returns the correct + index of the friend in the queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), @@ -117,6 +159,12 @@ def test_find_my_friend(self): @pytest.mark.task(taskno=3) def test_add_me_with_my_friends(self): + """ + Test the add_me_with_my_friends function to ensure it inserts the person + at the specified index and returns the updated queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), @@ -148,6 +196,12 @@ def test_add_me_with_my_friends(self): @pytest.mark.task(taskno=3) def test_add_me_with_my_friends_validate_queue(self): + """ + Test the add_me_with_my_friends function to validate that it mutates + the original queue and returns it. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), @@ -179,6 +233,12 @@ def test_add_me_with_my_friends_validate_queue(self): @pytest.mark.task(taskno=4) def test_remove_the_mean_person(self): + """ + Test the remove_the_mean_person function to ensure it removes the specified + person and returns the updated queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), @@ -210,6 +270,12 @@ def test_remove_the_mean_person(self): @pytest.mark.task(taskno=4) def test_remove_the_mean_person_validate_queue(self): + """ + Test the remove_the_mean_person function to validate that it mutates + the original queue and returns it. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), @@ -244,6 +310,12 @@ def test_remove_the_mean_person_validate_queue(self): @pytest.mark.task(taskno=5) def test_how_many_namefellows(self): + """ + Test the how_many_namefellows function to ensure it correctly counts + occurrences of a name in the queue. + + :param self: The test case instance. + """ test_data = [(['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Bucky'), (['Natasha', 'Steve', 'Ultron', 'Rocket'], 'Natasha'), (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Natasha')] @@ -263,6 +335,12 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): + """ + Test the remove_the_last_person function to ensure it removes and returns + the last person from the queue, mutating the queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), @@ -293,6 +371,12 @@ def test_remove_the_last_person(self): @pytest.mark.task(taskno=7) def test_sorted_names(self): + """ + Test the sorted_names function to ensure it returns a sorted copy + of the queue without mutating the original. + + :param self: The test case instance. + """ test_data =( (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), @@ -312,6 +396,12 @@ def test_sorted_names(self): @pytest.mark.task(taskno=7) def test_sorted_names_validate_queue(self): + """ + Test the sorted_names function to validate that it does not mutate + the original queue. + + :param self: The test case instance. + """ test_data = ( (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index f7b39ab..f0a6114 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -32,6 +32,7 @@ f'or forget to define it?') from None +# pylint: disable=C0301 # Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index f6b7af4..c2fd574 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -1,6 +1,7 @@ """Functions to prevent a nuclear meltdown.""" +# pylint: disable=C0301 def is_criticality_balanced(temperature, neutrons_emitted) -> bool: """Verify criticality is balanced. From 45e219a2dc49754cd8ebe4f1ac85f9434664f96e Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:54:37 -0700 Subject: [PATCH 08/29] Docstrings added --- collatz-conjecture/collatz_conjecture_test.py | 16 ++++- currency-exchange/exchange_test.py | 11 ++++ ghost-gobble-arcade-game/arcade_game_test.py | 58 ++++++++++++++++++- grains/grains_test.py | 27 ++++++++- guidos-gorgeous-lasagna/lasagna_test.py | 12 ++++ hello-world/hello_world_test.py | 13 +++++ leap/leap_test.py | 11 ++++ little-sisters-vocab/strings_test.py | 57 +++++++++++++----- making-the-grade/loops_test.py | 36 +++++++++--- meltdown-mitigation/conditionals_test.py | 15 +++-- square-root/square_root_test.py | 11 ++++ 11 files changed, 232 insertions(+), 35 deletions(-) diff --git a/collatz-conjecture/collatz_conjecture_test.py b/collatz-conjecture/collatz_conjecture_test.py index 306e3db..e748c37 100644 --- a/collatz-conjecture/collatz_conjecture_test.py +++ b/collatz-conjecture/collatz_conjecture_test.py @@ -1,3 +1,11 @@ +# pylint: disable=C0301 +""" +Unit tests for the steps function implementing the Collatz Conjecture. + +Tests include validation for zero, negative, even, odd values, and large numbers. +Ensures ValueError is raised for invalid input according to Collatz rules. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json # File last updated on 2023-07-20 @@ -10,6 +18,8 @@ class CollatzConjectureTest(unittest.TestCase): + """Unit tests for the functions related to the Collatz conjecture.""" + def test_zero_steps_for_one(self): self.assertEqual(steps(1), 0) @@ -26,10 +36,12 @@ def test_zero_is_an_error(self): with self.assertRaises(ValueError) as err: steps(0) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Only positive integers are allowed") + self.assertEqual(err.exception.args[0], + "Only positive integers are allowed") def test_negative_value_is_an_error(self): with self.assertRaises(ValueError) as err: steps(-15) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Only positive integers are allowed") + self.assertEqual(err.exception.args[0], + "Only positive integers are allowed") diff --git a/currency-exchange/exchange_test.py b/currency-exchange/exchange_test.py index 773b6fb..4a33853 100644 --- a/currency-exchange/exchange_test.py +++ b/currency-exchange/exchange_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for currency exchange functions. + +Tests cover calculations for currency exchange, change, bill values, bill counts, leftovers, and determining maximum currency exchangeable after applying spread and denomination constraints. + +Uses pytest and unittest for parametrized and task-specific testing. +""" + import unittest import pytest @@ -11,6 +20,8 @@ class CurrencyExchangeTest(unittest.TestCase): + """Unit tests for currency exchange utility functions.""" + @pytest.mark.task(taskno=1) def test_exchange_money(self): test_data = [(100000, 0.8), (700000, 10.0)] diff --git a/ghost-gobble-arcade-game/arcade_game_test.py b/ghost-gobble-arcade-game/arcade_game_test.py index 2ffdd3d..a775a5a 100644 --- a/ghost-gobble-arcade-game/arcade_game_test.py +++ b/ghost-gobble-arcade-game/arcade_game_test.py @@ -1,10 +1,66 @@ +# pylint: disable=C0301 +""" + +This `arcade_game_test.py` file is a comprehensive set of unit tests +for the Pac-Man-style "ghost gobble arcade game" logic found in +`arcade_game.py`. The test class uses Python's `unittest` framework +(with `pytest` markers for task organization) and checks the +correctness of each function (`eat_ghost`, `score`, `lose`, `win`) +provided by the game logic module. + +Structure & Coverage + +1. **eat_ghost** +- Tests if Pac-Man can eat a ghost only when: + - A power pellet is active **and** + - He's touching a ghost. +- Includes positive (should eat) and negative (should not eat) scenarios. + +2. **score** +- Tests Pac-Man scores when: + - Touching a dot. + - Touching a power pellet. + - Not scoring when touching neither. + +3. **lose** +- Tests losing logic: + - Should lose if touching a ghost without a power pellet. + - Should *not* lose if touching a ghost *with* a power pellet active. + - Should *not* lose if not touching a ghost. + +4. **win** +- Tests winning the game: + - Wins if *all dots eaten* and hasn't lost (touching ghost *without* pellet). + - Doesn't win if all dots eaten *but* touching a ghost without a pellet. + - Wins if all dots eaten *and* touching a ghost *with* a pellet (i.e., didn't lose). + - Doesn't win if not all dots eaten. + +General Comments + +- The error messages are descriptive to aid debugging if an assertion fails. +- Each test calls the appropriate function with various inputs to check all critical edge and task-specific cases. +- Follows best practices for test case isolation and clarity. + +Final Note + +This suite provides full behavioral coverage of the gameplay logic as +specified in `arcade_game.py`. To run these tests, simply execute the +file with a compatible test runner (pytest or python -m unittest). +Make sure the tested functions are correctly imported from the +`arcade_game` module (as in your header). + +If you need any enhancements, parameterizations, or help troubleshooting +failing test cases, let me know! +""" import unittest import pytest from arcade_game import eat_ghost, score, lose, win class GhostGobbleGameTest(unittest.TestCase): - + """ + Unit tests for the Ghost Gobble arcade game functions using unittest framework. + """ @pytest.mark.task(taskno=1) def test_ghost_gets_eaten(self): actual_result = eat_ghost(True, True) diff --git a/grains/grains_test.py b/grains/grains_test.py index 3c7f281..52f403a 100644 --- a/grains/grains_test.py +++ b/grains/grains_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for the grains module. + +Tests the square and total functions to ensure correct calculations +of grains on each square, total grains, and proper error handling for +invalid input. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json # File last updated on 2023-09-27 @@ -11,6 +20,15 @@ class GrainsTest(unittest.TestCase): + """ + Unit tests for the GrainsTest class, covering the square and + total functions from the grains module. + + Verifies correct grain counts for specific chessboard squares, + total grain calculation, and proper exception handling for + invalid input values. + """ + def test_grains_on_square_1(self): self.assertEqual(square(1), 1) @@ -48,19 +66,22 @@ def test_square_0_is_invalid(self): with self.assertRaises(ValueError) as err: square(0) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "square must be between 1 and 64") + self.assertEqual(err.exception.args[0], + "square must be between 1 and 64") def test_negative_square_is_invalid(self): with self.assertRaises(ValueError) as err: square(-1) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "square must be between 1 and 64") + self.assertEqual(err.exception.args[0], + "square must be between 1 and 64") def test_square_greater_than_64_is_invalid(self): with self.assertRaises(ValueError) as err: square(65) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "square must be between 1 and 64") + self.assertEqual(err.exception.args[0], + "square must be between 1 and 64") def test_returns_the_total_number_of_grains_on_the_board(self): self.assertEqual(total(), 18446744073709551615) diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index f0a6114..4d02fc7 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -1,3 +1,14 @@ +# pylint: disable=C0301 +""" +Unit tests for Guido's gorgeous lasagna module functions and constants. + +Covers presence and values of EXPECTED_BAKE_TIME, and correctness of +bake_time_remaining, preparation_time_in_minutes, and elapsed_time_in_minutes. +Also checks that all required docstrings are present. + +Raises informative import errors to help identify naming issues during testing. +""" + import unittest import pytest @@ -35,6 +46,7 @@ # pylint: disable=C0301 # Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): + """Unit tests for lasagna-related functionality.""" @pytest.mark.task(taskno=1) def test_EXPECTED_BAKE_TIME(self): diff --git a/hello-world/hello_world_test.py b/hello-world/hello_world_test.py index 5617b64..c804691 100644 --- a/hello-world/hello_world_test.py +++ b/hello-world/hello_world_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for the 'hello' function in hello_world.py. + +Ensures the function returns 'Hello, World!' as expected. +Provides informative error messages if the function is +missing or incorrectly implemented. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hello-world/canonical-data.json # File last updated on 2023-07-19 @@ -25,6 +34,10 @@ class HelloWorldTest(unittest.TestCase): + """ + Test case class for verifying HelloWorld functionality using unittest. + """ + def test_say_hi(self): msg = ("\n\nThis test expects a return of the string " "'Hello, World!' \nDid you use print('Hello, World!') " diff --git a/leap/leap_test.py b/leap/leap_test.py index 6a1d732..c06f951 100644 --- a/leap/leap_test.py +++ b/leap/leap_test.py @@ -1,3 +1,10 @@ +# pylint: disable=C0301 +""" +Unit tests for the leap_year function, verifying correct leap +year logic for various cases based on canonical Exercism +problem specifications. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json # File last updated on 2023-07-19 @@ -10,6 +17,10 @@ class LeapTest(unittest.TestCase): + """ + Unit tests for verifying leap year calculation functionality. + """ + def test_year_not_divisible_by_4_in_common_year(self): self.assertIs(leap_year(2015), False) diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py index 0c670df..dc45889 100644 --- a/little-sisters-vocab/strings_test.py +++ b/little-sisters-vocab/strings_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for the string manipulation functions in the 'strings' module. + +Tests adding the 'un' prefix, transforming word groups with a prefix, removing +the '-ness' suffix, and converting adjectives to verbs within sentences. +Uses pytest markers for task organization and detailed error messages for clarity. +""" + import unittest import pytest from strings import (add_prefix_un, @@ -7,18 +16,28 @@ class LittleSistersVocabTest(unittest.TestCase): + """ + Unit tests for vocabulary string manipulation functions in + the 'strings' module. + """ @pytest.mark.task(taskno=1) def test_add_prefix_un(self): - input_data = ['happy', 'manageable', 'fold', 'eaten', 'avoidable', 'usual'] + input_data = ['happy', 'manageable', 'fold', + 'eaten', 'avoidable', 'usual'] result_data = [f'un{item}' for item in input_data] - for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', word=word, expected=expected): + for variant, (word, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + word=word, + expected=expected): actual_result = add_prefix_un(word) error_message = (f'Called add_prefix_un("{word}"). ' - f'The function returned "{actual_result}", but the ' - f'tests expected "{expected}" after adding "un" as a prefix.') + f'The function returned "{actual_result}", ' + f'but the ' + f'tests expected "{expected}" after adding ' + f'"un" as a prefix.') self.assertEqual(actual_result, expected, msg=error_message) @@ -41,10 +60,12 @@ def test_make_word_groups_en(self): @pytest.mark.task(taskno=2) def test_make_word_groups_pre(self): - input_data = ['pre', 'serve', 'dispose', 'position', 'requisite', 'digest', - 'natal', 'addressed', 'adolescent', 'assumption', 'mature', 'compute'] - expected = ('pre :: preserve :: predispose :: preposition :: prerequisite :: ' - 'predigest :: prenatal :: preaddressed :: preadolescent :: preassumption :: ' + input_data = ['pre', 'serve', 'dispose', 'position', + 'requisite', 'digest', 'natal', 'addressed', + 'adolescent', 'assumption', 'mature', 'compute'] + expected = ('pre :: preserve :: predispose :: preposition :: ' + 'prerequisite :: predigest :: prenatal :: ' + 'preaddressed :: preadolescent :: preassumption :: ' 'premature :: precompute') actual_result = make_word_groups(input_data) @@ -57,7 +78,8 @@ def test_make_word_groups_pre(self): @pytest.mark.task(taskno=2) def test_make_word_groups_auto(self): - input_data = ['auto', 'didactic', 'graph', 'mate', 'chrome', 'centric', 'complete', + input_data = ['auto', 'didactic', 'graph', 'mate', + 'chrome', 'centric', 'complete', 'echolalia', 'encoder', 'biography'] expected = ('auto :: autodidactic :: autograph :: automate :: autochrome :: ' 'autocentric :: autocomplete :: autoecholalia :: autoencoder :: ' @@ -73,8 +95,9 @@ def test_make_word_groups_auto(self): @pytest.mark.task(taskno=2) def test_make_words_groups_inter(self): - input_data = ['inter', 'twine', 'connected', 'dependent', 'galactic', 'action', - 'stellar', 'cellular', 'continental', 'axial', 'operative', 'disciplinary'] + input_data = ['inter', 'twine', 'connected', 'dependent', + 'galactic', 'action', 'stellar', 'cellular', + 'continental', 'axial', 'operative', 'disciplinary'] expected = ('inter :: intertwine :: interconnected :: interdependent :: ' 'intergalactic :: interaction :: interstellar :: intercellular :: ' 'intercontinental :: interaxial :: interoperative :: interdisciplinary') @@ -103,8 +126,8 @@ def test_remove_suffix_ness(self): actual_result = remove_suffix_ness(word) error_message = (f'Called remove_suffix_ness("{word}"). ' f'The function returned "{actual_result}", ' - f'but the tests expected "{expected}" after the ' - 'suffix was removed.') + f'but the tests expected "{expected}" ' + f'after the suffix was removed.') self.assertEqual(actual_result, expected, msg=error_message) @@ -121,7 +144,8 @@ def test_adjective_to_verb(self): 'The black oil got on the white dog.'] index_data = [-2, -1, 3, 3, -2, -3, 5, 2, 1] result_data = ['brighten', 'darken', 'harden', 'soften', - 'lighten', 'dampen', 'shorten', 'weaken', 'blacken'] + 'lighten', 'dampen', 'shorten', 'weaken', + 'blacken'] for variant, (sentence, index, expected) in enumerate( zip(input_data, index_data, result_data), start=1): @@ -130,7 +154,8 @@ def test_adjective_to_verb(self): index=index, expected=expected): actual_result = adjective_to_verb(sentence, index) - error_message = (f'Called adjective_to_verb("{sentence}", {index}). ' + error_message = (f'Called adjective_to_verb("' + f'{sentence}", {index}). ' f'The function returned "{actual_result}", ' f'but the tests ' f'expected "{expected}" as the verb for ' diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py index f34b0dd..2fd534b 100644 --- a/making-the-grade/loops_test.py +++ b/making-the-grade/loops_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Test suite for validating functions from the 'loops' module related +to student exam score calculations, including rounding scores, +counting failed students, filtering scores above a threshold, +generating letter grade cutoffs, ranking students, and identifying +perfect scores. +""" + import unittest import pytest @@ -11,18 +20,23 @@ class MakingTheGradeTest(unittest.TestCase): + """ + Test case class for verifying functions in the making-the-grade module. + """ @pytest.mark.task(taskno=1) def test_round_scores(self): - # Because we the input list can be mutated, the test data has been created - # as tuples, which we then convert to a list when the test runs. - # this makes accurate error messages easier to create. + # Because we the input list can be mutated, the test data has + # been created as tuples, which we then convert to a list when + # the test runs. This makes accurate error messages easier to create. test_data = [tuple(), (.5,), (1.5,), - (90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3), - (50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, 91, 28.6, 88.0)] + (90.33, 40.5, 55.44, 70.05, 30.55, 25.45, + 80.45, 95.3, 38.7, 40.3), + (50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, + 91, 28.6, 88.0)] result_data = [[], [0], [2], @@ -41,7 +55,8 @@ def test_round_scores(self): error_message = (f'Called round_scores({list(student_scores)}). ' f'The function returned {sorted(actual_result)} ' f'after sorting, but ' - f'the tests expected {sorted(expected)} after sorting. ' + f'the tests expected {sorted(expected)} after ' + f'sorting. ' f'One or more scores were rounded incorrectly.') # everything is sorted for easier comparison. @@ -142,11 +157,14 @@ def test_student_ranking(self): @pytest.mark.task(taskno=6) def test_perfect_score(self): test_data = [ - [['Joci', 100], ['Vlad', 100], ['Raiana', 100], ['Alessandro', 100]], + [['Joci', 100], ['Vlad', 100], ['Raiana', 100], + ['Alessandro', 100]], [['Jill', 30], ['Paul', 73]], [], - [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], ['Alex', 42], - ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], ['Vlad', 55]], + [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], + ['Alex', 42], + ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], + ['Vlad', 55]], [['Yoshi', 52], ['Jan', 86], ['Raiana', 100], ['Betty', 60], ['Joci', 100], ['Kora', 81], ['Bern', 41], ['Rose', 94]] diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py index 6e9ab56..a348d8b 100644 --- a/meltdown-mitigation/conditionals_test.py +++ b/meltdown-mitigation/conditionals_test.py @@ -1,3 +1,11 @@ +# pylint: disable=C0301 +""" +Unit tests for the meltdown mitigation functions, verifying +correct behavior for criticality balance, reactor efficiency, +and fail safe status using a variety of boundary and +representative input values. +""" + import unittest import pytest from conditionals import (is_criticality_balanced, @@ -6,15 +14,14 @@ class MeltdownMitigationTest(unittest.TestCase): - """Test cases for Meltdown mitigation exercise. - """ + """Test cases for Meltdown mitigation exercise.""" @pytest.mark.task(taskno=1) def test_is_criticality_balanced(self): - """Testing border cases around typical points. + """ + Testing border cases around typical points. T, n == (800, 500), (625, 800), (500, 1000), etc. - """ test_data = ((750, 650, True), (799, 501, True), (500, 600, True), diff --git a/square-root/square_root_test.py b/square-root/square_root_test.py index 8f94940..89a2d6c 100644 --- a/square-root/square_root_test.py +++ b/square-root/square_root_test.py @@ -1,3 +1,10 @@ +# pylint: disable=C0301 +""" +Unit tests for the square_root function, verifying +correct calculation of integer square roots for various +inputs using test data from Exercism. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json # File last updated on 2023-07-19 @@ -10,6 +17,10 @@ class SquareRootTest(unittest.TestCase): + """ + Test suite for verifying the functionality of the square_root function. + """ + def test_root_of_1(self): self.assertEqual(square_root(1), 1) From 9aa621c00f57500e5b1f0f5a9cd04947ab5a76f8 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:55:04 -0700 Subject: [PATCH 09/29] Update lasagna_test.py --- guidos-gorgeous-lasagna/lasagna_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index 4d02fc7..07c23a4 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -49,7 +49,7 @@ class LasagnaTest(unittest.TestCase): """Unit tests for lasagna-related functionality.""" @pytest.mark.task(taskno=1) - def test_EXPECTED_BAKE_TIME(self): + def test_expected_bake_time(self): failure_msg = 'Expected a constant of EXPECTED_BAKE_TIME with a value of 40.' self.assertEqual(EXPECTED_BAKE_TIME, 40, msg=failure_msg) From f7ab7cd29a7aac6551ef7388415cdd96342b0b78 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 23:00:11 -0700 Subject: [PATCH 10/29] Update README.md --- README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6fd8298..7770891 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ -# Exercism Python Track +# [Exercism Python Track](https://exercism.io/my/tracks/python) -Learn and master concepts to achieve fluency in Python. +
+ + +
+ +## Exercism exercises in Python + +### About Exercism +Exercism is an online platform designed to help you improve your coding skills through practice and mentorship. + +Exercism provides you with thousands of exercises spread across numerous language tracks. Once you start a language track you are presented with a core set of exercises to complete. Each one is a fun and interesting challenge designed to teach you a little more about the features of a language. + +You complete a challenge by downloading the exercise to your computer and solving it in your normal working environment. Once you've finished you submit it online and one of our mentors will give you feedback on how you could improve it using features of the language that you may not be familiar with. After a couple of rounds of refactoring, your exercise will be complete and you will unlock both the next core exercise and also a series of related side-exercises for you to practice with. + +Exercism is entirely open source and relies on the contributions of thousands of wonderful people. + +Exercism is designed to be fun and friendly, and we place a strong emphasis on empathetic communication. + +Sign up and have fun. Exercism is 100% free :) -143 coding exercises for Python on Exercism. From Error Handling to ISBN Verifier. From 4446b5d5a50d0f2143dc092ce0e533965545466d Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 19:57:27 -0700 Subject: [PATCH 11/29] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 9d883d9..2c8c785 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.10", "3.12"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From 7136c784ffd874cddda544482806c08169029da7 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:11:49 -0700 Subject: [PATCH 12/29] # pylint: disable=C0116 --- guidos-gorgeous-lasagna/lasagna_test.py | 2 +- hello-world/hello_world_test.py | 1 + leap/leap_test.py | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index 07c23a4..6a21967 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -43,7 +43,7 @@ f'or forget to define it?') from None -# pylint: disable=C0301 +# pylint: disable=C0301, C0116 # Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): """Unit tests for lasagna-related functionality.""" diff --git a/hello-world/hello_world_test.py b/hello-world/hello_world_test.py index c804691..811bf6a 100644 --- a/hello-world/hello_world_test.py +++ b/hello-world/hello_world_test.py @@ -33,6 +33,7 @@ ) from None +# pylint: disable=C0116 class HelloWorldTest(unittest.TestCase): """ Test case class for verifying HelloWorld functionality using unittest. diff --git a/leap/leap_test.py b/leap/leap_test.py index c06f951..42e425a 100644 --- a/leap/leap_test.py +++ b/leap/leap_test.py @@ -16,10 +16,9 @@ ) +# pylint: disable=C0116 class LeapTest(unittest.TestCase): - """ - Unit tests for verifying leap year calculation functionality. - """ + """Unit tests for verifying leap year calculation functionality.""" def test_year_not_divisible_by_4_in_common_year(self): self.assertIs(leap_year(2015), False) From 9e5d829f2c31bd2c2620064e5a3bc7eeeaa012b3 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:15:52 -0700 Subject: [PATCH 13/29] # pylint: disable=C0116 --- collatz-conjecture/collatz_conjecture_test.py | 1 + currency-exchange/exchange_test.py | 1 + grains/grains_test.py | 1 + hello-world/hello_world.py | 1 + little-sisters-vocab/strings_test.py | 1 + making-the-grade/loops_test.py | 1 + meltdown-mitigation/conditionals_test.py | 1 + square-root/square_root_test.py | 1 + 8 files changed, 8 insertions(+) diff --git a/collatz-conjecture/collatz_conjecture_test.py b/collatz-conjecture/collatz_conjecture_test.py index e748c37..1b2b3f5 100644 --- a/collatz-conjecture/collatz_conjecture_test.py +++ b/collatz-conjecture/collatz_conjecture_test.py @@ -17,6 +17,7 @@ ) +# pylint: disable=C0116 class CollatzConjectureTest(unittest.TestCase): """Unit tests for the functions related to the Collatz conjecture.""" diff --git a/currency-exchange/exchange_test.py b/currency-exchange/exchange_test.py index 4a33853..7f1f495 100644 --- a/currency-exchange/exchange_test.py +++ b/currency-exchange/exchange_test.py @@ -19,6 +19,7 @@ exchangeable_value) +# pylint: disable=C0116 class CurrencyExchangeTest(unittest.TestCase): """Unit tests for currency exchange utility functions.""" diff --git a/grains/grains_test.py b/grains/grains_test.py index 52f403a..526319a 100644 --- a/grains/grains_test.py +++ b/grains/grains_test.py @@ -19,6 +19,7 @@ ) +# pylint: disable=C0116 class GrainsTest(unittest.TestCase): """ Unit tests for the GrainsTest class, covering the square and diff --git a/hello-world/hello_world.py b/hello-world/hello_world.py index d695ea1..5efcac4 100644 --- a/hello-world/hello_world.py +++ b/hello-world/hello_world.py @@ -1,2 +1,3 @@ +# pylint: disable=C0116 def hello(): return 'Hello, World!' diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py index dc45889..1b2999c 100644 --- a/little-sisters-vocab/strings_test.py +++ b/little-sisters-vocab/strings_test.py @@ -15,6 +15,7 @@ adjective_to_verb) +# pylint: disable=C0116 class LittleSistersVocabTest(unittest.TestCase): """ Unit tests for vocabulary string manipulation functions in diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py index 2fd534b..47d6a34 100644 --- a/making-the-grade/loops_test.py +++ b/making-the-grade/loops_test.py @@ -19,6 +19,7 @@ perfect_score) +# pylint: disable=C0116 class MakingTheGradeTest(unittest.TestCase): """ Test case class for verifying functions in the making-the-grade module. diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py index a348d8b..f0a4b8e 100644 --- a/meltdown-mitigation/conditionals_test.py +++ b/meltdown-mitigation/conditionals_test.py @@ -13,6 +13,7 @@ fail_safe) +# pylint: disable=C0116 class MeltdownMitigationTest(unittest.TestCase): """Test cases for Meltdown mitigation exercise.""" diff --git a/square-root/square_root_test.py b/square-root/square_root_test.py index 89a2d6c..fcd2bb7 100644 --- a/square-root/square_root_test.py +++ b/square-root/square_root_test.py @@ -16,6 +16,7 @@ ) +# pylint: disable=C0116 class SquareRootTest(unittest.TestCase): """ Test suite for verifying the functionality of the square_root function. From 409aae5ec4a98b463f363b49a3044a0ecfc4add3 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:16:51 -0700 Subject: [PATCH 14/29] Update arcade_game_test.py --- ghost-gobble-arcade-game/arcade_game_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost-gobble-arcade-game/arcade_game_test.py b/ghost-gobble-arcade-game/arcade_game_test.py index a775a5a..336eefd 100644 --- a/ghost-gobble-arcade-game/arcade_game_test.py +++ b/ghost-gobble-arcade-game/arcade_game_test.py @@ -1,4 +1,4 @@ -# pylint: disable=C0301 +# pylint: disable=C0301, C0116 """ This `arcade_game_test.py` file is a comprehensive set of unit tests From e491b56e9fc91d9c8dfc968fc06d99b3b219985b Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:20:13 -0700 Subject: [PATCH 15/29] Update hello_world.py --- hello-world/hello_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hello-world/hello_world.py b/hello-world/hello_world.py index 5efcac4..4d0815d 100644 --- a/hello-world/hello_world.py +++ b/hello-world/hello_world.py @@ -1,3 +1,3 @@ -# pylint: disable=C0116 +# pylint: disable=C0116, C0114 def hello(): return 'Hello, World!' From 4161efa7b2ab594d44d05dd522a20a9c20c32903 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:20:15 -0700 Subject: [PATCH 16/29] Update .pylintrc --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index fcbba09..e0ba4ac 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,3 @@ [MASTER] ignore=.venv +ignore-paths=*/solutions/* \ No newline at end of file From 1341cb0421b762353dd5420a0a09ee396cf64957 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:23:03 -0700 Subject: [PATCH 17/29] Update .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index e0ba4ac..78aa507 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] ignore=.venv -ignore-paths=*/solutions/* \ No newline at end of file +ignore-paths=.*/solutions/.* \ No newline at end of file From d8b9a716c5cc97a1c8c8e66a9dba4fd16b2ee39c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:29:57 -0700 Subject: [PATCH 18/29] Update .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 78aa507..97d344e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] ignore=.venv -ignore-paths=.*/solutions/.* \ No newline at end of file +ignore-paths=^(.*/|)solutions/.*$ \ No newline at end of file From 3f235013f1cae11b94cae2a7e2fbe13638600282 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:31:56 -0700 Subject: [PATCH 19/29] Update conditionals.py --- meltdown-mitigation/conditionals.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index c2fd574..271d883 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -18,7 +18,8 @@ def is_criticality_balanced(temperature, neutrons_emitted) -> bool: def reactor_efficiency(voltage, current, theoretical_max_power) -> str: - """Assess reactor efficiency zone. + """ + Assess reactor efficiency zone. :param voltage: int or float - voltage value. :param current: int or float - current value. @@ -41,9 +42,11 @@ def reactor_efficiency(voltage, current, theoretical_max_power) -> str: if efficiency < 30: return 'black' - elif 30 <= efficiency < 60: + + if 30 <= efficiency < 60: return 'red' - elif 60 <= efficiency < 80: + + if 60 <= efficiency < 80: return 'orange' return 'green' From f30d1ee02ec61f3036953ba6c4da2f0cb5f1b058 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:47:29 -0700 Subject: [PATCH 20/29] Create config.json --- triangle/.exercism/config.json | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 triangle/.exercism/config.json diff --git a/triangle/.exercism/config.json b/triangle/.exercism/config.json new file mode 100644 index 0000000..041bf28 --- /dev/null +++ b/triangle/.exercism/config.json @@ -0,0 +1,36 @@ +{ + "authors": [], + "contributors": [ + "behrtam", + "BethanyG", + "cmccandless", + "Dog", + "ikhadykin", + "kytrinyx", + "lowks", + "mpatibandla", + "N-Parsons", + "Nishant23", + "pheanex", + "rozuur", + "sjakobi", + "Stigjb", + "tqa236", + "xitanggg", + "YuriyCherniy" + ], + "files": { + "solution": [ + "triangle.py" + ], + "test": [ + "triangle_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", + "source": "The Ruby Koans triangle project, parts 1 & 2", + "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com" +} From 6b476bf5661b7c75362456b24da84d87a7e456eb Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:47:35 -0700 Subject: [PATCH 21/29] Update conditionals.py --- meltdown-mitigation/conditionals.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index 271d883..7029096 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -3,7 +3,8 @@ # pylint: disable=C0301 def is_criticality_balanced(temperature, neutrons_emitted) -> bool: - """Verify criticality is balanced. + """ + Verify criticality is balanced. :param temperature: int or float - temperature value in kelvin. :param neutrons_emitted: int or float - number of neutrons emitted per second. @@ -53,7 +54,8 @@ def reactor_efficiency(voltage, current, theoretical_max_power) -> str: def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: - """Assess and return status code for the reactor. + """ + Assess and return status code for the reactor. :param temperature: int or float - value of the temperature in kelvin. :param neutrons_produced_per_second: int or float - neutron flux. From bd80626983e63bd73da5e13d6a3cc7189fb0ea93 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:11:30 -0700 Subject: [PATCH 22/29] Create pytest.yml --- .github/workflows/pytest.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..0065102 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,28 @@ +name: Pytest Workflow + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + + - name: Run pytest + run: pytest --verbose --ignore=solutions \ No newline at end of file From b96847851aa580494c4ff1f4f199b5ee3c5cf5b8 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:12:57 -0700 Subject: [PATCH 23/29] Update pytest.yml --- .github/workflows/pytest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 0065102..c9c9d86 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,6 +22,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -r requirements.txt pip install pytest - name: Run pytest From 564ba7d76fb88b340ad66061e85dadd7d03f4a64 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:15:17 -0700 Subject: [PATCH 24/29] Update pylint.yml --- .github/workflows/pylint.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 2c8c785..4e975a7 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,6 +1,10 @@ name: Pylint -on: [push] +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] jobs: build: From d6d6bbe2254d2e868ada1a234a1722d643dc3848 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:24:27 -0700 Subject: [PATCH 25/29] Triangle --- triangle/.exercism/metadata.json | 1 + triangle/HELP.md | 130 +++++++++++++++++++++++++++++++ triangle/README.md | 60 ++++++++++++++ triangle/triangle.py | 49 ++++++++++++ triangle/triangle_test.py | 80 +++++++++++++++++++ 5 files changed, 320 insertions(+) create mode 100644 triangle/.exercism/metadata.json create mode 100644 triangle/HELP.md create mode 100644 triangle/README.md create mode 100644 triangle/triangle.py create mode 100644 triangle/triangle_test.py diff --git a/triangle/.exercism/metadata.json b/triangle/.exercism/metadata.json new file mode 100644 index 0000000..0b73088 --- /dev/null +++ b/triangle/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"triangle","id":"6f8c14ada1ce403c975161c5a18528c2","url":"https://exercism.org/tracks/python/exercises/triangle","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/triangle/HELP.md b/triangle/HELP.md new file mode 100644 index 0000000..2d9233f --- /dev/null +++ b/triangle/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit triangle.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/triangle/README.md b/triangle/README.md new file mode 100644 index 0000000..961c673 --- /dev/null +++ b/triangle/README.md @@ -0,0 +1,60 @@ +# Triangle + +Welcome to Triangle on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Determine if a triangle is equilateral, isosceles, or scalene. + +An _equilateral_ triangle has all three sides the same length. + +An _isosceles_ triangle has at least two sides the same length. +(It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) + +A _scalene_ triangle has all sides of different lengths. + +## Note + +For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. + +In equations: + +Let `a`, `b`, and `c` be sides of the triangle. +Then all three of the following expressions must be true: + +```text +a + b ≥ c +b + c ≥ a +a + c ≥ b +``` + +See [Triangle Inequality][triangle-inequality] + +[triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality + +## Source + +### Contributed to by + +- @behrtam +- @BethanyG +- @cmccandless +- @Dog +- @ikhadykin +- @kytrinyx +- @lowks +- @mpatibandla +- @N-Parsons +- @Nishant23 +- @pheanex +- @rozuur +- @sjakobi +- @Stigjb +- @tqa236 +- @xitanggg +- @YuriyCherniy + +### Based on + +The Ruby Koans triangle project, parts 1 & 2 - https://web.archive.org/web/20220831105330/http://rubykoans.com \ No newline at end of file diff --git a/triangle/triangle.py b/triangle/triangle.py new file mode 100644 index 0000000..4d60e40 --- /dev/null +++ b/triangle/triangle.py @@ -0,0 +1,49 @@ +"""Determine if a triangle is equilateral, isosceles, or scalene.""" + + +def equilateral(sides: list) -> bool: + """ + An equilateral triangle has all three sides the same length. + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] == sides[1] == sides[2] + return False + + +def isosceles(sides: list) -> bool: + """ + An isosceles triangle has at least two sides the same length. + (It is sometimes specified as having exactly two sides the same length, + but for the purposes of this exercise we'll say at least two.) + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] + return False + + +def scalene(sides: list) -> bool: + """ + A scalene triangle has all sides of different lengths. + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] + return False + + +def inequality_violation(sides: list) -> bool: + """ + Let a, b, and c be sides of the triangle. + Then all three of the following expressions must be true: + + a + b ≥ c + b + c ≥ a + a + c ≥ b + """ + return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] + + +def check_for_zeroes(sides: list) -> bool: + """No zeroes allowed.""" + if 0 in sides: + return False + return True diff --git a/triangle/triangle_test.py b/triangle/triangle_test.py new file mode 100644 index 0000000..b279c83 --- /dev/null +++ b/triangle/triangle_test.py @@ -0,0 +1,80 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json +# File last updated on 2023-07-19 + +import unittest + +from triangle import ( + equilateral, + isosceles, + scalene, +) + + +class EquilateralTriangleTest(unittest.TestCase): + def test_all_sides_are_equal(self): + self.assertIs(equilateral([2, 2, 2]), True) + + def test_any_side_is_unequal(self): + self.assertIs(equilateral([2, 3, 2]), False) + + def test_no_sides_are_equal(self): + self.assertIs(equilateral([5, 4, 6]), False) + + def test_all_zero_sides_is_not_a_triangle(self): + self.assertIs(equilateral([0, 0, 0]), False) + + def test_sides_may_be_floats(self): + self.assertIs(equilateral([0.5, 0.5, 0.5]), True) + + +class IsoscelesTriangleTest(unittest.TestCase): + def test_last_two_sides_are_equal(self): + self.assertIs(isosceles([3, 4, 4]), True) + + def test_first_two_sides_are_equal(self): + self.assertIs(isosceles([4, 4, 3]), True) + + def test_first_and_last_sides_are_equal(self): + self.assertIs(isosceles([4, 3, 4]), True) + + def test_equilateral_triangles_are_also_isosceles(self): + self.assertIs(isosceles([4, 4, 4]), True) + + def test_no_sides_are_equal(self): + self.assertIs(isosceles([2, 3, 4]), False) + + def test_first_triangle_inequality_violation(self): + self.assertIs(isosceles([1, 1, 3]), False) + + def test_second_triangle_inequality_violation(self): + self.assertIs(isosceles([1, 3, 1]), False) + + def test_third_triangle_inequality_violation(self): + self.assertIs(isosceles([3, 1, 1]), False) + + def test_sides_may_be_floats(self): + self.assertIs(isosceles([0.5, 0.4, 0.5]), True) + + +class ScaleneTriangleTest(unittest.TestCase): + def test_no_sides_are_equal(self): + self.assertIs(scalene([5, 4, 6]), True) + + def test_all_sides_are_equal(self): + self.assertIs(scalene([4, 4, 4]), False) + + def test_first_and_second_sides_are_equal(self): + self.assertIs(scalene([4, 4, 3]), False) + + def test_first_and_third_sides_are_equal(self): + self.assertIs(scalene([3, 4, 3]), False) + + def test_second_and_third_sides_are_equal(self): + self.assertIs(scalene([4, 3, 3]), False) + + def test_may_not_violate_triangle_inequality(self): + self.assertIs(scalene([7, 3, 2]), False) + + def test_sides_may_be_floats(self): + self.assertIs(scalene([0.5, 0.4, 0.6]), True) From 6256a73bbbcbbc5560f1dd012d87afb9e3f81acb Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:36:33 -0700 Subject: [PATCH 26/29] Update triangle_test.py --- triangle/triangle_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triangle/triangle_test.py b/triangle/triangle_test.py index b279c83..b76a82f 100644 --- a/triangle/triangle_test.py +++ b/triangle/triangle_test.py @@ -11,6 +11,7 @@ ) +# pylint: disable=C0116 class EquilateralTriangleTest(unittest.TestCase): def test_all_sides_are_equal(self): self.assertIs(equilateral([2, 2, 2]), True) @@ -28,6 +29,7 @@ def test_sides_may_be_floats(self): self.assertIs(equilateral([0.5, 0.5, 0.5]), True) +# pylint: disable=C0116 class IsoscelesTriangleTest(unittest.TestCase): def test_last_two_sides_are_equal(self): self.assertIs(isosceles([3, 4, 4]), True) @@ -57,6 +59,7 @@ def test_sides_may_be_floats(self): self.assertIs(isosceles([0.5, 0.4, 0.5]), True) +# pylint: disable=C0116 class ScaleneTriangleTest(unittest.TestCase): def test_no_sides_are_equal(self): self.assertIs(scalene([5, 4, 6]), True) From 1f1bc6ac9c422ef8247d72e39b4342b650934627 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:36:36 -0700 Subject: [PATCH 27/29] Update triangle.py --- triangle/triangle.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/triangle/triangle.py b/triangle/triangle.py index 4d60e40..ead093f 100644 --- a/triangle/triangle.py +++ b/triangle/triangle.py @@ -5,7 +5,7 @@ def equilateral(sides: list) -> bool: """ An equilateral triangle has all three sides the same length. """ - if check_for_zeroes(sides) and inequality_violation(sides): + if all_sides_positive(sides) and no_inequality_violation(sides): return sides[0] == sides[1] == sides[2] return False @@ -16,7 +16,7 @@ def isosceles(sides: list) -> bool: (It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) """ - if check_for_zeroes(sides) and inequality_violation(sides): + if all_sides_positive(sides) and no_inequality_violation(sides): return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] return False @@ -25,12 +25,12 @@ def scalene(sides: list) -> bool: """ A scalene triangle has all sides of different lengths. """ - if check_for_zeroes(sides) and inequality_violation(sides): + if all_sides_positive(sides) and no_inequality_violation(sides): return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] return False -def inequality_violation(sides: list) -> bool: +def no_inequality_violation(sides: list) -> bool: """ Let a, b, and c be sides of the triangle. Then all three of the following expressions must be true: @@ -42,8 +42,9 @@ def inequality_violation(sides: list) -> bool: return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] -def check_for_zeroes(sides: list) -> bool: - """No zeroes allowed.""" - if 0 in sides: - return False - return True +def all_sides_positive(sides: list) -> bool: + """ + No zeroes or negative numbers allowed. + All triangles should have 3 sides. + """ + return all(side > 0 for side in sides) and len(sides) == 3 From c76b668eb59746a9f518c46fcecff2f70eab1f65 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:37:04 -0700 Subject: [PATCH 28/29] Update triangle.py --- triangle/triangle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/triangle/triangle.py b/triangle/triangle.py index ead093f..6537e75 100644 --- a/triangle/triangle.py +++ b/triangle/triangle.py @@ -39,7 +39,9 @@ def no_inequality_violation(sides: list) -> bool: b + c ≥ a a + c ≥ b """ - return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] + return (sides[0] + sides[1] >= sides[2] and + sides[0] + sides[2] >= sides[1] and + sides[2] + sides[1] >= sides[0]) def all_sides_positive(sides: list) -> bool: From 86f9ddda8c1b2d2b34c2f781857226d2bbdbeca0 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:39:03 -0700 Subject: [PATCH 29/29] Update triangle_test.py --- triangle/triangle_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/triangle/triangle_test.py b/triangle/triangle_test.py index b76a82f..9312b90 100644 --- a/triangle/triangle_test.py +++ b/triangle/triangle_test.py @@ -1,3 +1,5 @@ +# pylint: disable=C0116, C0114, C0115 + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json # File last updated on 2023-07-19 @@ -11,7 +13,7 @@ ) -# pylint: disable=C0116 +# pylint: disable=C0116, C0114, C0115 class EquilateralTriangleTest(unittest.TestCase): def test_all_sides_are_equal(self): self.assertIs(equilateral([2, 2, 2]), True)