diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index eb18b4638..eba8a7be2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -54,6 +54,7 @@ about: A template PR for code review with a checklist - [ ] The function's name describes it's behavior - [ ] The function's name matches the file name + - _It's ok to have extra helper functions if necessary, like with mergesort_ - [ ] The function has correct type annotations - [ ] The function is not called at the top level of the function file - _Recursive solutions **can** call the function from **inside** the function body_ @@ -68,7 +69,7 @@ about: A template PR for code review with a checklist ### Don'ts -- [ ] The function's strategy _is not_ described in the documentation +- [ ] The function's strategy _is not_ described in any docstrings or tests - [ ] Comments explain the _strategy_, **not** the _implementation_ - [ ] The function _does not_ have more comments than code - If it does, consider finding a new strategy or a simpler implementation diff --git a/.vscode/settings.json b/.vscode/settings.json index 16a2e8728..5bcd6d90e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -84,4 +84,4 @@ "Khayri", "Linah" ] -} +} \ No newline at end of file diff --git a/solutions/binary_to_decimal.py b/solutions/binary_to_decimal.py new file mode 100644 index 000000000..ef3b44459 --- /dev/null +++ b/solutions/binary_to_decimal.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for converting binary numbers to decimal. + +Module contents: + - binary_to_decimal: converts binary numbers to decimal. + +Created on 29/12/2024 +@author: Khusro Sakhi +""" + + +def binary_to_decimal(binary_str: str) -> int: + """ + Converts a binary number represented as a string into its decimal equivalent. + + Parameters: + binary_str: A string representing the binary number (e.g., '1010') + + Returns: + The decimal equivalent of the binary number as an integer + + Raises: + TypeError: If the input is not a string or None. + ValueError: If the input contains non-binary characters. + + >>> binary_to_decimal ("1010") + 10 + + >>> binary_to_decimal ("1") + 1 + + >>> binary_to_decimal ("100") + 4 + """ + + if not isinstance(binary_str, str): + raise TypeError("Entered value is not a string.") + if not binary_str: + raise ValueError("Input binary string cannot be empty.") + if not all(bit in "01" for bit in binary_str): + raise ValueError("Entered string contains non-binary characters.") + + decimal = 0 + length = len(binary_str) + for i, bit in enumerate(binary_str): + bit_value = int(bit) + + decimal += bit_value * (2 ** (length - i - 1)) + return decimal diff --git a/solutions/count_digits.py b/solutions/count_digits.py new file mode 100644 index 000000000..9704f2860 --- /dev/null +++ b/solutions/count_digits.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Module to count the number of digits in a given number. + +Module contents: + - count_digits: Counts the number of digits in a given number. + +Created on Wednesday, 1st January, 2025. +@author: Gai Samuel +""" + + +def count_digits(number: int | float | str | bool) -> int: + """ + The count_digits function counts the number of digits in a given number. + + Parameters: + number: an integer/string/ boolean or float number whose digits are to be counted. + + Returns: + An integer representing the number of digits in the input number. + Note that the negative sign is not counted as a digit. + + Raises: + ValueError: + If the input is empty. + If the input is not an integer, float, or numeric string. + TypeError: + if there is an invalid input. + + Example: + >>> count_digits(34) + 2 + >>> count_digits(-17) + 2 + >>> count_digits(0) + 1 + >>> count_digits(19.5) + 2 + >>> count_digits("24") + 2 + """ + # Check for empty input + if number == "": + raise ValueError("The input cannot be empty.") + + # Converts a boolean to an integer. + if isinstance(number, bool): + number = int(number) + + # converts a float to an integer. + if isinstance(number, float): + number = int(number) + + # Handles a string input but raises a ValueError if the string is not a number. + if isinstance(number, str): + try: + number = int(number) + except ValueError as exc: + raise ValueError("Input must be a valid number.") from exc + + # Check for empty or invalid inputs + if not isinstance(number, (int, float, str, bool)): + raise TypeError("Input must be an integer, float, boolean or numeric string.") + + # Convert the number to a string and remove the negative sign if present. + number_str = str(abs(int(number))) + + return len(number_str) diff --git a/solutions/distinct_subsequences.py b/solutions/distinct_subsequences.py new file mode 100644 index 000000000..0bd8a8d89 --- /dev/null +++ b/solutions/distinct_subsequences.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for calculating the number of distinct subsequences of one +string (target_str) in another string (source_str). +(LeetCode problem copied from +https://leetcode.com/problems/distinct-subsequences/) + +Module contents: + - distinct_subsequences: Computes the number of + distinct subsequences of target_str in source_str. + - _count_subsequences_recursive: A helper function to compute subsequences. + +Dependencies: + - functools: Used for the `cache` decorator. + +Created on 26 12 2024 +@author: Mohamed-Elnageeb +""" + +import sys +from functools import cache + +sys.setrecursionlimit(10**6) # Increase the limit to support deep recursion + + +def distinct_subsequences(source_str: str, target_str: str) -> int: + """ + Computes the number of distinct subsequences of string target_str + in string source_str. + + This function calculates the number of ways string target_str + can appear as a subsequence in source_str. + + Parameters: + source_str: str + The source string in which to find subsequences. + target_str: str + The target string to match as a subsequence. + + Returns: + int: The number of distinct subsequences of target_str in source_str. + + Raises: + TypeError: If the inputs are not strings. + + Examples: + >>> distinct_subsequences("rabbbit", "rabbit") + 3 + + >>> distinct_subsequences("abc", "abc") + 1 + + >>> distinct_subsequences("abc", "def") + 0 + + >>> distinct_subsequences("aaa", "aa") + 3 + """ + # Validate input + if not isinstance(source_str, str): + raise TypeError("Input source_str must be a string.") + if not isinstance(target_str, str): + raise TypeError("Input target_str must be a string.") + + @cache + def _count_subsequences_recursive(source_idx: int, target_idx: int) -> int: + """ + A helper function to compute the number of subsequences. + + This function calculates how many distinct ways + the substring target_str[target_idx:] can appear as a subsequence + in the substring source_str[source_idx:]. + + Parameters: + source_idx: int + Current index in string source_str. + target_idx: int + Current index in string target_str. + + Returns: + int: The number of ways target_str[target_idx:] can appear + as a subsequence in source_str[source_idx:]. + """ + # Base case: All characters of target_str are matched + if target_idx == len(target_str): + return 1 + # Base case: End of source_str reached without matching all + # characters of target_str + if source_idx == len(source_str): + return 0 + + # Recursive case: Exclude the current character of source_str + result = _count_subsequences_recursive(source_idx + 1, target_idx) + + # Recursive case: Include the current character of source_str if it + # matches target_str[target_idx] + if source_str[source_idx] == target_str[target_idx]: + result += _count_subsequences_recursive(source_idx + 1, target_idx + 1) + + return result + + # Start the recursion from the beginning of both strings + return _count_subsequences_recursive(0, 0) diff --git a/solutions/factorial_calculator.py b/solutions/factorial_calculator.py index 279f6c00d..281466673 100644 --- a/solutions/factorial_calculator.py +++ b/solutions/factorial_calculator.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- """ Module Name: factorial_calculator + Description: This module provides a function to calculate the factorial of a given number. -It uses a recursive approach to compute the factorial. The module includes: the following function: - factorial_calculator(num): Returns the factorial of the input number num. @@ -15,7 +15,7 @@ def factorial_calculator(num: int | float) -> int: """ - This function calculates the factorial of a non-negative integer or whole float using recursion. + This function calculates the factorial of a non-negative integer or whole float. The factorial of a non-negative integer n is the product of all positive integers less than or equal to num until we reach one. @@ -64,6 +64,6 @@ def factorial_calculator(num: int | float) -> int: if num == 1: # Base case 2 return 1 # turn-around 2 - # Recursive case: factorial(num) = num * factorial(num-1) + # Recursive case: factorial_calculator(num) = num * factorial_calculator(num-1) # breaking-down num | Build-up by multiplying return num * factorial_calculator(num - 1) diff --git a/solutions/intersection_of_two.py b/solutions/intersection_of_two.py index b28aa53ac..fd956d050 100644 --- a/solutions/intersection_of_two.py +++ b/solutions/intersection_of_two.py @@ -23,10 +23,6 @@ def intersection_of_two(first_list: list, second_list: list) -> list: can contain any type of items that are allowed in a list (e.g., integers, strings, floats, special characters, etc.) - Note: - This function could be simplified using built-in functions like set(), - which automatically handle duplicates and set intersections. - However, I wanted to write the logic manually to demonstrate how it works step-by-step Parameters: first_list (list): The first list of items diff --git a/solutions/is_leap_year.py b/solutions/is_leap_year.py new file mode 100644 index 000000000..cd89c1062 --- /dev/null +++ b/solutions/is_leap_year.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for determining whether the given year is leap year or not + +Module contents: + - is_leap: determines whether the given year is leap year or not. + +Created on 29/12/2024 +@author: Khusro Sakhi +""" + + +def is_leap_year(year: int) -> bool: + """Determines if the given year is a leap year. + + Parameters: + year (int): The year to test. + + Returns: + bool: True if the year is a leap year, False otherwise. + + Raises: + TypeError: If the input is not an integer. + ValueError: If the input is less than or equal to 0. + + >>> is_leap_year(2000) + True + + >>> is_leap_year(1990) + False + + >>> is_leap_year(2100) + False + + >>> is_leap_year(2004) + True + """ + if not isinstance(year, int): + raise TypeError("Entered year is not an integer.") + if year <= 0: + raise ValueError("Year must be greater than 0.") + + # A leap year is divisible by 4, but not by 100 unless also divisible by 400 + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) diff --git a/solutions/sort_colors.py b/solutions/sort_colors.py index 0a2db45f9..8e5543742 100644 --- a/solutions/sort_colors.py +++ b/solutions/sort_colors.py @@ -4,37 +4,22 @@ A module for sorting a list of numbers based on their colors. Module contents: - - sort_colors: sorts a list of integers representing colors (0, 1, 2) in ascending order. + - sort_colors: Sorts a list of integers representing colors (0, 1, 2) in ascending order. Dependencies: - typing: Used for type annotations (List). -Notes: - - Does not use built-in sorting functions or libraries such as `sorted()` or `sort()`. - - Implements an in-place counting sort approach. - - Time complexity: O(n) - - Space complexity: O(1) (in-place). - Created on 26 12 2024 @author: Mohamed-Elnageeb """ -from typing import List # Importing List for type annotations +from typing import List def sort_colors(nums: List[int]) -> None: """ Sorts a given list of integers representing colors (0, 1, 2) in-place. - This function implements sorting logic manually using a counting sort approach and does not use - any built-in sorting functions or libraries, such as `sorted()` or `sort()`. - - Time Complexity: - O(n): Iterates through the list once to count occurrences and again to modify it in-place. - - Space Complexity: - O(1): Uses a fixed-size counter list of size 3. No additional space is used. - Parameters: nums: List[int] A list of integers where each integer is 0, 1, or 2. Modified in-place. @@ -43,7 +28,8 @@ def sort_colors(nums: List[int]) -> None: None Raises: - AssertionError: if the input is not a list or contains invalid elements (numbers out of the range 0 to 2). + TypeError: If the input is not a list or if elements in the list are not integers. + AssertionError: If elements in the list are outside the range 0 to 2. Examples: >>> nums = [0, 2, 1] @@ -61,18 +47,22 @@ def sort_colors(nums: List[int]) -> None: >>> nums [0, 1, 1, 2, 2] """ - # Validate input - assert isinstance(nums, list), "Input must be a list." - assert all(0 <= number <= 2 for number in nums), "All elements must be 0, 1, or 2." + # Validate input type + if not isinstance(nums, list): + raise TypeError("Input must be a list.") + if not all(isinstance(number, int) for number in nums): + raise TypeError("All elements in the list must be integers.") - # Create a counter list to store the count of each number - counter_list = [0] * 3 + # Validate input range + if not all(0 <= number <= 2 for number in nums): + raise AssertionError("All elements must be integers in the range 0, 1, or 2.") - # Count occurrences of each number + # Count occurrences of 0, 1, and 2 + counter_list = [0] * 3 for number in nums: counter_list[number] += 1 - # Rebuild the nums list in-place + # Rebuild the list based on counts index = 0 for number in range(3): for _ in range(counter_list[number]): diff --git a/solutions/sum_range.py b/solutions/sum_range.py new file mode 100644 index 000000000..f7c547573 --- /dev/null +++ b/solutions/sum_range.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# --#coding: utf-8 -- +""" +Description: This file contains the test cases for the sum_range function. + +This module defines the sum_range function that calculates the sum of all +integers from start to end (inclusive). The function raises a TypeError if +start or end is not an integer. + +date:2024-12-31 +@ author: Zeinab Mommed +""" + + +def sum_range(start: int, end: int) -> int: + """ + Calculate the sum of all integers from start to end (inclusive). + parameters: + start (int): The starting integer. + end (int): The ending integer. + Returns: + int: The sum of all integers from start to end (inclusive). + + Raises: + TypeError: If start or end is not an integer e.g. float, string. + + Example: + + >>>sum_range(1, 20) + 210 + + >>>sum_range(-10, 10) + 0 + + >>>sum_range(10, 1) + 55 + + >>>sum_range(5, 5) + 5 + + >>>sum_range(100, 10000) + 50005000 + """ + # Check if start and end are integers + if not isinstance(start, int) or not isinstance(end, int): + raise TypeError("Both start and end must be integers.") + # Ensure start is less than or equal to end + if start > end: + start, end = end, start + # Calculate the sum of the range + total = 0 + for number in range(start, end + 1): + total += number + return total diff --git a/solutions/tests/test_binary_to_decimal.py b/solutions/tests/test_binary_to_decimal.py new file mode 100644 index 000000000..ea295b8e0 --- /dev/null +++ b/solutions/tests/test_binary_to_decimal.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for binary_to_decimal function. + +Test categories: + - Standard cases: Typical binary numbers with various lengths and values. + - Edge cases: Binary strings with leading zeros, empty strings, or single digits. + - Defensive tests: Tests for invalid input types (e.g., non-strings, floats, `None`) + and assertions (e.g., strings containing non-binary characters). + + + +Created on 29/12/2024 + +@author: Khusro Sakhi +""" + +import unittest + +from ..binary_to_decimal import binary_to_decimal + + +class TestBinaryToDecimal(unittest.TestCase): + """Test suite for the binary_to_decimal function.""" + + # Standard test cases + def test_binary_1010(self): + """It should return 10 for the binary '1010'.""" + self.assertEqual(binary_to_decimal("1010"), 10) + + def test_binary_0(self): + """It should return 0 for the binary '0'.""" + self.assertEqual(binary_to_decimal("0"), 0) + + def test_binary_1(self): + """It should return 1 for the binary '1'.""" + self.assertEqual(binary_to_decimal("1"), 1) + + def test_binary_1111(self): + """It should return 15 for the binary '1111'.""" + self.assertEqual(binary_to_decimal("1111"), 15) + + def test_binary_100000(self): + """It should return 32 for the binary '100000'.""" + self.assertEqual(binary_to_decimal("100000"), 32) + + # Edge cases + def test_empty_string(self): + """It should raise a ValueError for an empty string.""" + with self.assertRaises(ValueError): + binary_to_decimal("") + + def test_leading_zeros(self): + """It should correctly handle binary strings with leading zeros.""" + self.assertEqual(binary_to_decimal("000101"), 5) + + # Large number test + def test_large_binary_number(self): + """It should correctly convert a very large binary number.""" + self.assertEqual( + binary_to_decimal("11111111111111111111111111111111111111111111111111"), + 1125899906842623, + ) + + # Defensive tests + def test_invalid_characters(self): + """It should raise a ValueError for non-binary characters.""" + with self.assertRaises(ValueError): + binary_to_decimal("10A01") + + def test_non_string_input(self): + """It should raise a TypeError for non-string input.""" + with self.assertRaises(TypeError): + binary_to_decimal(1010) + + def test_none_input(self): + """It should raise a TypeError for None input.""" + with self.assertRaises(TypeError): + binary_to_decimal(None) + + def test_float_input(self): + """It should raise a TypeError for a float input.""" + with self.assertRaises(TypeError): + binary_to_decimal(10.1) + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_count_digits.py b/solutions/tests/test_count_digits.py new file mode 100644 index 000000000..2652f12f7 --- /dev/null +++ b/solutions/tests/test_count_digits.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Module containing tests for the count_digits function. + +created on Wednesday, 1st January, 2025. +@author: Gai Samuel +""" + +import unittest + +from ..count_digits import count_digits + + +class TestCountDigits(unittest.TestCase): + """Tests for count_digits function""" + + def test_positive_integer(self): + """It returns the number of digits in a positive integer.""" + self.assertEqual(count_digits(24), 2) + + def test_negative_integer(self): + """It returns the number of digits in a negative integer.""" + self.assertEqual(count_digits(-2024), 4) + + def test_zero(self): + """It returns 1 for zero since it has one digit.""" + self.assertEqual(count_digits(0), 1) + + def test_large_integer(self): + """It returns the number of digits in a large integer""" + self.assertEqual(count_digits(12345678910), 11) + + def test_float_input(self): + """It converts float to an integer and then returns corresponding number of digits.""" + self.assertEqual(count_digits(45.9), 2) + + def test_string_input(self): + """It returns the number of digits in a string.""" + self.assertEqual(count_digits("15"), 2) + + def test_empty_input(self): + """It raises a value error for an empty input""" + with self.assertRaises(ValueError): + count_digits("") + + def test_invalid_string_input(self): + """It raises a value error for an invalid string input""" + with self.assertRaises(ValueError): + count_digits("Gai") + + def test_list_input(self): + """It raises a type error for a list input""" + with self.assertRaises(TypeError): + count_digits([1, 2, 3]) + + def test_negative_float(self): + """It returns the number of digits in a negative float.""" + self.assertEqual(count_digits(-45.9), 2) + + def test_none_input(self): + """It raises a type error for a None input""" + with self.assertRaises(TypeError): + count_digits(None) + + def test_dict_input(self): + """It raises a type error for a dictionary input""" + with self.assertRaises(TypeError): + count_digits({"name": "Gai"}) + + def test_true_boolean_input(self): + """It returns the number of digits in a True boolean.""" + self.assertEqual(count_digits(True), 1) + + def test_false_boolean_input(self): + """It returns the number of digits in a False boolean.""" + self.assertEqual(count_digits(False), 1) + + def test_set_input(self): + """It raises a type error for a set input""" + with self.assertRaises(TypeError): + count_digits({1, 2, 3}) diff --git a/solutions/tests/test_distinct_subsequences.py b/solutions/tests/test_distinct_subsequences.py new file mode 100644 index 000000000..e23fc53de --- /dev/null +++ b/solutions/tests/test_distinct_subsequences.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for the distinct_subsequences function. +Contains comprehensive tests to ensure the correctness +of the distinct_subsequences function. + +Test categories: + - Standard cases: Common strings with matching subsequences, + non-matching strings. + - Edge cases: Empty target_str string, empty source_str string, + both strings empty, repeated characters. + - Performance tests: Large strings to verify efficiency with memoization. + - Defensive tests: Non-string inputs, invalid types for + source_str and target_str. + +Notes on Memoization: + - The distinct_subsequences function uses the @cache decorator to store + previously computed results for specific subproblems. + - This avoids redundant computations for overlapping subproblems, + significantly reducing the time complexity + from exponential to polynomial. + - The performance tests verify that the implementation efficiently handles + large input sizes, which would otherwise be + infeasible without memoization. + +Created on 02 01 2025 +Author: Mohamed-Elnageeb +""" + +import unittest + +from ..distinct_subsequences import distinct_subsequences + + +class TestCountDistinctSubsequences(unittest.TestCase): + """Test suite for the distinct_subsequences function.""" + + # Standard cases + def test_matching_subsequence(self): + """Test matching subsequences in typical strings.""" + source_str = "rabbbit" + target_str = "rabbit" + self.assertEqual(distinct_subsequences(source_str, target_str), 3) + + def test_no_matching_subsequence(self): + """Test when there are no matching subsequences.""" + source_str = "abc" + target_str = "def" + self.assertEqual(distinct_subsequences(source_str, target_str), 0) + + def test_full_match(self): + """Test when source_str matches target_str exactly.""" + source_str = "abc" + target_str = "abc" + self.assertEqual(distinct_subsequences(source_str, target_str), 1) + + # Edge cases + + def test_empty_target(self): + """Test when the target_str is empty (always one way to match).""" + source_str = "abc" + target_str = "" + self.assertEqual(distinct_subsequences(source_str, target_str), 1) + + def test_empty_source(self): + """Test when the source_str is empty (cannot match anything).""" + source_str = "" + target_str = "abc" + self.assertEqual(distinct_subsequences(source_str, target_str), 0) + + def test_both_empty(self): + """Test when both source_str and target_str are empty.""" + source_str = "" + target_str = "" + self.assertEqual(distinct_subsequences(source_str, target_str), 1) + + def test_repeated_characters(self): + """Test with repeated characters in source_str and target_str.""" + source_str = "aaa" + target_str = "aa" + self.assertEqual(distinct_subsequences(source_str, target_str), 3) + + def test_with_special_characters(self): + """Test with special characters in source_str and target_str.""" + source_str = "a aa,,b" + target_str = "a ,b" + self.assertEqual(distinct_subsequences(source_str, target_str), 2) + + def test_case_sensitivity(self): + """Test case sensitivity in source_str and target_str.""" + source_str = "aB BAGBB" + target_str = "AB" + self.assertEqual(distinct_subsequences(source_str, target_str), 2) + + # Performance tests + + def test_large_strings(self): + """Test with large strings to verify efficiency.""" + source_str = "a" * 1000 + target_str = "a" * 10 + result = distinct_subsequences(source_str, target_str) + self.assertEqual( + result, 263409560461970212832400 + ) # number of ways to choose 10 a's from 1000 a's + + # Defensive tests + + def test_non_string_source(self): + """Test when source_str is not a string.""" + source_str = 123 + target_str = "abc" + with self.assertRaises(TypeError): + distinct_subsequences(source_str, target_str) + + def test_non_string_target(self): + """Test when target_str is not a string.""" + source_str = "abc" + target_str = [1, 2, 3] + with self.assertRaises(TypeError): + distinct_subsequences(source_str, target_str) + + def test_both_non_strings(self): + """Test when both source_str and target_str are not strings.""" + source_str = 123 + target_str = True + with self.assertRaises(TypeError): + distinct_subsequences(source_str, target_str) + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_is_leap_year.py b/solutions/tests/test_is_leap_year.py new file mode 100644 index 000000000..0adac81a9 --- /dev/null +++ b/solutions/tests/test_is_leap_year.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for is_leap function. + +Test categories: + - Standard cases: Common leap and non-leap years + - Edge cases: Smallest leap year, non-leap years close to leap years + - Defensive tests: Handling of invalid inputs (e.g., non-integer, negative values) + +Created on 29/12/2024 + +@author: Khusro Sakhi +""" + +import unittest + +from ..is_leap_year import is_leap_year + + +class TestLeapYear(unittest.TestCase): + """Test suite for the is_leap function - contains buggy tests!""" + + # Standard test cases + def test_leap_year_2000(self): + """It should return True for the year 2000 (divisible by 400)""" + self.assertTrue(is_leap_year(2000)) + + def test_non_leap_year_1990(self): + """It should return False for the year 1990 (not divisible by 4)""" + self.assertFalse(is_leap_year(1990)) + + def test_non_leap_year_2100(self): + """It should return False for the year 2100 (divisible by 100 but not by 400)""" + self.assertFalse(is_leap_year(2100)) + + def test_leap_year_2004(self): + """It should return True for the year 2004 (divisible by 4 but not by 100)""" + self.assertTrue(is_leap_year(2004)) + + # Edge cases + def test_leap_year_4(self): + """It should return True for the year 4 (the smallest leap year)""" + self.assertTrue(is_leap_year(4)) + + def test_non_leap_year_1(self): + """It should return False for the year 1 (not divisible by 4)""" + self.assertFalse(is_leap_year(1)) + + def test_non_leap_year_100(self): + """It should return False for the year 100 (divisible by 100 but not by 400)""" + self.assertFalse(is_leap_year(100)) + + # Defensive tests + def test_string_input(self): + """It should raise TypeError for a string input""" + with self.assertRaises(TypeError): + is_leap_year("2000") + + def test_negative_year(self): + """It should raise ValueError for a negative year""" + with self.assertRaises(ValueError): + is_leap_year(-4) + + def test_none_input(self): + """It should raise TypeError for None input""" + with self.assertRaises(TypeError): + is_leap_year(None) + + def test_zero_input(self): + """It should raise ValueError for 0 input""" + with self.assertRaises(ValueError): + is_leap_year(0) + + def test_float_input(self): + """It should raise TypeError for a float input""" + with self.assertRaises(TypeError): + is_leap_year(4.5) + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_sort_colors.py b/solutions/tests/test_sort_colors.py index aa7af4392..282d5ad8e 100644 --- a/solutions/tests/test_sort_colors.py +++ b/solutions/tests/test_sort_colors.py @@ -21,9 +21,11 @@ - test_randomly_generated_list: Tests with a randomly generated list. - Exception test cases: - - test_not_a_list: Ensures an AssertionError is raised for non-list inputs. - - test_invalid_elements: Ensures an AssertionError is raised for invalid - elements (e.g., elements not in the range [0, 2]). + - test_not_a_list: Ensures a TypeError is raised for non-list inputs. + - test_invalid_elements_type: Ensures a TypeError is raised for invalid + element types (e.g., non-integer elements). + - test_invalid_elements_range: Ensures an AssertionError is raised for invalid + elements (e.g., integers not in the range [0, 2]). Created on 26 10 2024 @@ -77,13 +79,19 @@ def test_large_list(self): self.assertEqual(nums, expected) def test_not_a_list(self): - """It should raise an assertion error if the input is not a list""" - with self.assertRaises(AssertionError): + """It should raise a TypeError if the input is not a list""" + with self.assertRaises(TypeError): sort_colors("not a list") - def test_invalid_elements(self): - """It should raise an assertion error for invalid elements""" - nums = [0, 1, 3] # Invalid element 3 + def test_invalid_elements_type(self): + """It should raise a TypeError for invalid element types""" + nums = [0, 1, "2"] # Invalid element "2" (string) + with self.assertRaises(TypeError): + sort_colors(nums) + + def test_invalid_elements_range(self): + """It should raise an AssertionError for elements out of range""" + nums = [0, 1, 3] # Invalid element 3 (out of range) with self.assertRaises(AssertionError): sort_colors(nums) diff --git a/solutions/tests/test_sum_range.py b/solutions/tests/test_sum_range.py new file mode 100644 index 000000000..0ea274ff8 --- /dev/null +++ b/solutions/tests/test_sum_range.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# --#coding: utf-8 -- +""" +Description: This file contains the test cases for the sum_range function. + +The function sum_range(start, end) calculates the sum of all integers from +start to end (inclusive). And raises a TypeError if start or end is not +an integer. + +Tests cases: +- standard test cases : test_positive_range, test_reversed_range, +test_single_number, test_negative_range, test_negative_range_reversed, +test_large_range, test_large_range_reversed +- Exception test cases : test_float_range, test_string_range, test_mixed_range +- Performance test cases : test_large_range, test_large_range_reversed +- Boundary test cases : test_single_number, test_negative_range, +-error test cases : test_float_range, test_string_range, test_mixed_range + +date:2024-12-31 +@author: Zeinab Mommed +""" + +import unittest +from solutions.sum_range import sum_range + + +class TestSumRange(unittest.TestCase): + """Tests for the sum_range function.""" + + # Standard test cases + def test_positive_range(self): + """Test with a normal positive range.""" + self.assertEqual(sum_range(1, 5), 15) + + def test_reversed_range(self): + """Test when start > end (reversed input).""" + self.assertEqual(sum_range(5, 1), 15) + + # Boundary test cases + def test_single_number(self): + """Test a range with a single number.""" + self.assertEqual(sum_range(10, 10), 10) + + def test_negative_range(self): + """Test a range that includes negative numbers.""" + self.assertEqual(sum_range(-3, 3), 0) + + def test_negative_range_reversed(self): + """Test a range that includes negative numbers (reversed input).""" + self.assertEqual(sum_range(3, -3), 0) + + def test_large_range(self): + """Test with a large range.""" + self.assertEqual(sum_range(1, 100), 5050) + + def test_large_range_reversed(self): + """Test with a large range.""" + self.assertEqual(sum_range(100, 1), 5050) + + # Defensive test cases + def test_float_range(self): + """Test with a range that includes floats.""" + with self.assertRaises(TypeError): + sum_range(0.5, 3.5) + + def test_string_range(self): + """Test with a range that includes strings.""" + with self.assertRaises(TypeError): + sum_range("1", "5") + + def test_mixed_range(self): + """Test with a range that includes a mix of strings and integers.""" + with self.assertRaises(TypeError): + sum_range(1, "5") + + def test_error_range(self): + """Test with a range that includes a mix of float and integers.""" + with self.assertRaises(TypeError): + sum_range(1, 5.5)