diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6e269b4ad..330a04cbc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -55,7 +55,6 @@ 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 - [ ] The function has correct type annotations -- [ ] The function is not called in the function file ## Strategy @@ -83,7 +82,5 @@ about: A template PR for code review with a checklist - [ ] Variable names are clear and helpful - [ ] The code follows the strategy as simply as possible - [ ] The implementation is as simple as possible given the strategy -- [ ] There are no commented lines of code -- [ ] There are no `print` statements anywhere - [ ] The code includes defensive assertions - [ ] Defensive assertions include as little logic as possible diff --git a/.vscode/settings.json b/.vscode/settings.json index 252022b48..32bd7f818 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ //-------- Editor configuration -------- // Controls auto save of editors that have unsaved changes.: https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save - "files.autoSave": "onFocusChange", + "files.autoSave": "off", // Format a file on save. A formatter must be available. "editor.formatOnSave": true, @@ -122,5 +122,17 @@ "source.fixAll.ruff": "explicit", "source.organizeImports.ruff": "explicit" } - } + }, + "cSpell.words": [ + "Gibril", + "Nouman", + "Osama", + "Rafaa", + "Reem", + "Safia", + "Mayar", + "Tilal" + "Mayar", + "Testdraw" + ] } diff --git a/README.md b/README.md index e69de29bb..39735afd6 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,92 @@ +# Collabor8 - MIT Emerging Talent ET6 Group 27 + +We are **Collabor8**, a team formed as part of the + **MIT Emerging Talent program**. Our group consists of: + +- Omnia Mustafa *(Group Leader)* +- Ahmed Goutbi *(Vice Leader)* +- Ayham Hasan +- Mohamad Tilal +- Mayar Agab +- Reem Osama +- Safa Ishag +- Safia Gibril Nouman +- Saja Abu Safia +- Rafaa Ali + +We are collaborating on a GitHub project to enhance our understanding and +application of version control while improving our programming skills. + +## Project Overview + +This project focuses on solving a selected set of problems while learning how +to effectively use GitHub for version control. The selection process for these +problems mimicked the **Secret Santa** game: + +1. We discussed each member's strengths and weaknesses, level of experience, and +knowledge. +2. Based on this discussion, a list of problems was created, ensuring that each +participant received two problems chosen by another member. + +### Learning Goals + +- Gain hands-on experience with GitHub workflows. +- Collaborate effectively within a team. +- Improve programming skills through problem-solving and peer reviews. +- Provide constructive feedback on each other's code. + +More on Learning Goals in `collaberations/learning_goals.md` + +## Workflow + +Here is a step-by-step guide to our workflow: + +1. **Branch Creation**: Create a new branch named after your problem file. +2. **File Addition**: Add your problem file(s) to the branch. +3. **Issue Creation**: Create an issue for the problem and a corresponding card +on the project board. +4. **Pull Request**: Once the problem is solved, add the solutions with +corresponding test file as a commit, and create a pull request (PR) to merge +your changes into the main branch. +5. **Card Update**: Update the card on the project board and mark it as "Ready +for Review." +6. **Peer Review**: Another member will review your code and provide feedback. +7. **Merge**: After addressing any feedback, your PR can be merged into the main +branch. + +## Collaboration Tools + +- **GitHub Project Board**: Tracks the progress of issues and pull requests. +- **Slack**: Main Communication tool +- **Google Meet**: Meetings three times a week, or according to requirements. +- **Collaboration Directory**: Contains additional details about: + - Collaboration process. + - Learning objectives. + - Meeting notes. + +## Meetings and Sessions + +We conducted several meetings and one dedicated session on GitHub usage. During +these, we discussed: + +- General GitHub commands. +- Best practices for our workflow. +- Troubleshooting common issues. + +Meetings times and agenda are discussed a few days before the date to schedule +best time that suits the majority of the team. Voting is done through **Polly**, +a third party service of Slack. + +For more information refer to `collaberations/communication.md` + +## Contact + +For any questions or further information, please refer to the files in the +collaboration directory or contact any group member. + +Happy collaborating! + +--- + +**MIT Emerging Talent ET6 Group 27 - Collabor8** + diff --git a/collaboration/README.md b/collaboration/README.md index 20889b951..64a2ebba7 100644 --- a/collaboration/README.md +++ b/collaboration/README.md @@ -1,5 +1,40 @@ # Collaboration +## The summary + +Our group is committed to fostering trust, collaboration, and open communication + by valuing diverse perspectives, respecting + time, and embracing accountability. We aim to create a positive and inclusive + environment where everyone feels supported, empowered to contribute, and aligned + toward shared goals. We strive to build strong relationships and ensure + collective growth where mistakes are treated as learning opportunities, and + trust is rebuilt through consistent actions and open dialogue. Together, we + are dedicated to making this a meaningful and enjoyable experience for all. +## The list + +1. Build and Sustain Trust: Commit to creating a reliable and supportive +environment where trust +underpins all interactions. +2. Foster Open Communication: Ensure a culture where all voices are valued, and + ideas, concerns, +and feedback are openly shared. +3. Respect Time and Focus: Honor each other’s time by striving for purposeful + and efficient +communication and collaboration. +4. Promote Team Growth: Support collective learning, personal development, and +the achievement +of shared goals. +5. Encourage Inclusivity: Create a space where diverse perspectives are +welcomed and everyone +feels empowered to contribute. +6. Ensure Clarity and Alignment: Maintain shared understanding and alignment on + goals, expectations, +and progress. +7. Cultivate a Positive Team Culture: Build a collaborative and motivating +environment that values +both achievements and relationships. +8. Embrace Growth from Challenges: Approach mistakes and challenges as +opportunities for learning and improvement. diff --git a/collaboration/communication.md b/collaboration/communication.md index 484652e0f..2e86ae05e 100644 --- a/collaboration/communication.md +++ b/collaboration/communication.md @@ -9,39 +9,144 @@ # Communication -______________________________________________________________________ +As a team we agreed that we will make a poll in Slack +to check the validity of all the team, and will take the highest vote +as the meeting time. And before ending the meeting will discuss +about what do we want to do, what will be our next step +and what is the timeline for these tasks. +We use Google Meet as the meeting channel, +while Slack is the channel for quick questions, +meetings plans and friendly communication channel. +We use GitHub as the basic channel for coding, +rase a challenge or any professional communication. -## Communication Schedule +## Meeting Agendas -| Day | How | The topic of discussion | | --- | :-: | ----------------------- | -| | | | +### First Meeting -## Communication Channels +#### Date: December 20, 2024 -how often will we get in touch on each channel, and what we will discuss there: +##### Overview of the first meeting -- **Issues**: -- **Pull Requests**: -- **Slack/Discord**: -- **Video Calls**: +We held an introductory meeting where everyone introduced themselves. +Our main focus was on two documents: +"Defining Group Norms" and "Tools of Collaborative Development." -______________________________________________________________________ +##### Key Points of the first meeting -## Availability +- Defining Group Norms: +Members should contribute by answering the questions +in the brainstorm section, adding their responses as bullet points, +and commenting with their names. +- Tools of Collaborative Development: +Members should pick a subject from the document, +add three points, and write an explanation at the bottom. +- Next Steps: +Review and contribute to both documents. +Vote in the next meeting poll to schedule it. -### Availability for calling/messaging +### Second Meeting -| Day | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday | | ------- | :----: | :-----: | :-------: | :------: | :----: | :------: | :----: | -| _name_ | | | | | | | | +#### Date: December 21, 2024 -### How many hours everyone has per day +##### Overview of the second meeting -- name: _5h_; -- name: _6h_; -- name: _5h_; -- name: _4h_; -- name: _3h_; +In this meeting, we reviewed the "Defining Group Norms" +and "Tools of Collaborative Development" documents, +planned the repository, and discussed next steps for code review +and the group name. + +##### Key Points of the second meeting + +- Document Updates + Agreed on updates to the "Defining Group Norms" +and "Tools of Collaborative Development" documents. +Members who haven’t continued their contributions to the +"Tools of Collaborative Development" file should do so. +- Repository Progress: + Mohamed Tilal would summarize the brainstorming ideas +to be pushed later. +- Group Name: + Members should participate in the poll for the group name +and add suggestions if desired. +- Next Meeting Plans: + The next meeting would be a walkthrough with +Reem Osama on GitHub. + Problem selection for the code review will include suggestions +from Reem’s game. + Members should vote in the poll to finalize the next meeting time. + +### Third Meeting + +#### Date: December 24, 2024 + +##### Overview of the third meeting + +The meeting focused on a review session for GitHub +and the key functions we’ll use for the project. + +###### Key Points of the third meeting + +- GitHub Review: +Omnia Mustafa - Abdulgadir outlined the main +functions in a post in Slack. +Reem Osama provided a detailed file containing all the notes, +which would serve as a reference moving forward. +- Acknowledgment: +Special thanks to Reem Osama for her incredible effort +in the session and for preparing and sharing the notes. +Your contribution is greatly appreciated! +- Next Meeting: +Next meeting would focus on choosing the problems and +discussing our game plan moving forward. + +### Forth Meeting + +#### Date: December 25, 2024 + +##### Overview of the forth meeting + +This meeting was all about understanding each other's challenges +and identifying the person who will create a challenge for who. + +##### What's next + +- Omnia Mustafa - Abdulgadir wrote down everyone's experiences +and perspectives on programming. +- Reem Osama provided the link to find out +who will create the challenge for who. +- Once know the person, each person the team fill out +the second form to communicate their challenges. + +### Fifth Meeting + +#### Date: December 29, 2024 + +##### Overview of the fifth meeting + +The meeting focused on task assignments, +problem workflows, and planning the next steps. + +##### Key Points of the fifth meeting + +###### Task Assignments + +- Safia Gibril Nouman would assign two problems +to Reem Osama and Rafaa ALI. +- Ahmed would work on the Learning Goals and Constraints files. +- Omnia Mustafa - Abdulgadir would contribute to the Collaborative file. +- Reem Osama would collaborate on the README file. + +###### Problem Workflow + + By December 30, 2024, each member must: + +- Create a new Python file named after their assigned problem. +- Create a branch, add the file to it, and push it. +- Create an issue for the problem with the same name and add it to the project board. +After completing these steps, members have until January 2, 2025, +to solve their assigned problems, allowing the team to +start reviewing them afterward. ## Asking for Help @@ -50,5 +155,5 @@ stubbornly getting no where. Here is a general guide for when to ask for help based on how long you've been stuck on the same problem: 1. _0 -> 30 min_: Try on your own -1. _30 -> 60 min_: Ask your group for help -1. _60+ min_: Tag your coaches in Slack or GitHub +2. _30 -> 60 min_: Ask your group for help +3. _60+ min_: Tag your coaches in Slack or GitHub diff --git a/collaboration/constraints.md b/collaboration/constraints.md index 24079505c..f1a506e49 100644 --- a/collaboration/constraints.md +++ b/collaboration/constraints.md @@ -1,34 +1,65 @@ -# Constraints - -Some boundaries around our project. - -## External - - - -## Internal: Involuntary - - - -## Internal: Voluntary - - +# Constraints + +This document outlines the constraints for our collaborative project, helping us +focus on realistic goals and effective teamwork. Each member will solve two +problems by writing Python code, documenting it, and testing it. Additionally, +every member will review two problems written by others. The repository is a +group effort, reflecting everyone's contributions. + +## External + + + +- **Project Deadlines**: The project must be completed by **January 10**, with +internal deadlines for milestones ensuring consistent progress. +- **Technologies**: The project requires Python, GitHub, VSCode, and linting +tools for coding, collaboration, and quality assurance. +- **Time Constraints**: Members have limited availability due to full-time jobs +and university schedules. + +## Internal: Involuntary + + + +- **Skill Levels**: The team includes members with a mix of beginner and experienced +skill levels, with most members new to coding. +- **Time Availability**: Each member can dedicate only a few hours per week due +to other commitments. + +## Internal: Voluntary + + + +- **Problem Solving**: Each member will solve two problems by writing Python +code, ensuring the solutions are well-documented and thoroughly tested. +- **Code Reviews**: Every member will review two problems written by others, +providing constructive feedback to enhance quality and learning. +- **Coding Conventions**: The team has agreed on consistent coding styles and a +code review checklist to maintain clarity and quality in the repository. +- **Repository Collaboration**: All members will contribute to a shared GitHub +repository, promoting teamwork and collective ownership of the project. +- **Scope Management**: Realistic project goals have been set to balance +learning opportunities with completing a high-quality final product. +- **Tools Commitment**: The team will use GitHub for version control, VSCode for +development, and Python linting tools to ensure clean, efficient code. +- **Feedback Cycles**: A process for iterative feedback and reviews has been +established to improve both individual skills and project outcomes. diff --git a/collaboration/learning_goals.md b/collaboration/learning_goals.md index 11c583d2b..511ec7f87 100644 --- a/collaboration/learning_goals.md +++ b/collaboration/learning_goals.md @@ -1,5 +1,42 @@ -# Learning Goals +# Learning Goals -## Collective +Our team comprises ten members with diverse backgrounds, including those new to +coding and others with significant experience. This variety presents an excellent +opportunity for mutual learning and growth. Below are the learning goals we have +collectively identified for these exercises: -## Individual +## Objectives for Team Members New to Coding + +- **Build foundational knowledge**: Understand the basics of data structures (DSs) + and algorithms. +- **Learn programming best practices**: Focus on writing clean, readable, and well- + documented code. +- **Practice problem-solving**: Develop confidence in tackling coding problems +step by step. +- **Gain exposure to code review**: Learn how to give and receive constructive feedback + to improve solutions. + +## Objectives for Experienced Team Members + +- **Strengthen advanced skills**: Dive deeper into complex DSs and algorithms to +enhance problem-solving capabilities. +- **Experiment with new approaches**: Explore and apply new programming language +features to refine solutions. +- **Focus on optimization**: Work on producing efficient and scalable code. +- **Mentor and collaborate**: Guide less experienced team members to create an +inclusive and supportive learning environment. + +## Shared Team Objectives + +- **Learn through collaboration**: Share knowledge and perspectives to maximize +learning outcomes for everyone. +- **Improve development speed**: Practice writing solutions faster while maintaining +quality. +- **Foster a culture of feedback**: Embrace multiple cycles of review to improve +individual and team skills. +- **Document solutions effectively**: Ensure every solution is well-commented and +easy to understand for future reference. + +These goals reflect our commitment to leveraging our diverse skills and experiences +to grow together as a team. We’ll revisit and refine these objectives as needed +to ensure alignment with our progress and expectations. diff --git a/collaboration/retrospective.md b/collaboration/retrospective.md index 74e18813b..f5e0f1fa1 100644 --- a/collaboration/retrospective.md +++ b/collaboration/retrospective.md @@ -1,23 +1,130 @@ - +# **Project Retrospective** -# Retrospective +This retrospective document summarizes the team's experience, +achievements, and lessons learned during this project cycle. -## Stop Doing +--- -## Continue Doing +## **What Went Well** -## Start Doing +- **Timing & Schedule Adherence**: The team consistently adhered to the + schedule,completing the project ahead of time. +- **Collaboration**: Teamwork was exceptional, with strong collaboration + and active participation from all members. +- **Communication**: Open, transparent communication fostered + a positive and supportive environment. +- **Team Dynamics**: Members formed strong friendships and + created a safe, welcoming atmosphere for collaboration. +- **Task Contributions**: Every member contributed meaningfully, + creating a healthy and efficient workflow. +- **Supportive Environment**: Everyone was willing to assist each + other, ensuring no one was left behind. -## Lessons Learned +--- -______________________________________________________________________ +## **What Didn’t Go as Planned** -## Strategy vs. Board +- **Code Quality Checks**: CI checks were sometimes overlooked +- before merging branches, leading to issues in others’ code. +- **Task Distribution**: Tasks could have been distributed more + evenly among team members. +- **Skill Building**: More focus on coding fundamentals + (e.g., Python basics) was desired, as most workshops emphasized GitHub usage. +- **Workflow Alignment**: The project board alignment could have + been improved for clarity. +- **Time Management**: A few tasks were delayed due + to personal circumstances, highlighting the need for better time tracking. -### What parts of your plan went as expected? +--- -### What parts of your plan did not work out? +## **Unexpected Surprises** -### Did you need to add things that weren't in your strategy? +- **Team Compatibility**: The level of compatibility and cooperation + among team members exceeded expectations. +- **Cultural Connections**: Discovering shared cultural backgrounds + and personal histories created a deeper bond. +- **Willingness to Help**: Team members showed exceptional willingness + to support one another. +- **Friendliness**: Getting to know the team was easy due to their + approachable and collaborative nature. -### Or remove extra steps? +--- + +## **Team Ratings** + +- **Communication and Collaboration**: 4.55/5 average rating. +- **Leadership Effectiveness**: Leadership was highly effective and + supportive, with excellent coordination. + +--- + +## **Challenges and Improvements** + +### **Challenges Encountered** + +- Some inconsistencies in workflow processes (e.g., CI checks, issue handling). +- Occasional delays in task delivery due to individual circumstances. +- Balancing skill levels among experienced and beginner team members. + +### **Improvements Suggested** + +- Distribute tasks more equitably among team members. +- Introduce periodic skill-sharing and mentoring sessions to balance knowledge levels. +- Conduct retrospectives to reflect on learnings and refine workflows. +- Implement structured CI checks to avoid errors in merged branches. + +--- + +## **Key Skills Gained** + +- **Technical**: Proficiency in GitHub, command-line tools, and basic algorithms. +- **Collaboration**: Improved communication, problem-solving, + and teamwork in remote settings. +- **Coding Practices**: Enhanced understanding of coding standards and efficiency. +- **Leadership**: Confidence in guiding and supporting others. + +--- + +## **Future Recommendations** + +### **For Team Collaboration** + +- Continue fostering open communication and collaboration. +- Provide tailored support to address individual strengths and weaknesses. +- Organize more friendly meetings to maintain a positive team spirit. + +### **For Leadership** + +- Assign backup leaders for meetings if the main leader is unavailable. +- Enhance task detailing and ensure equitable workload distribution. + +### **For Workflow** + +- Begin projects earlier to reduce anxiety and ensure ample planning time. +- Periodically review workflows for continuous improvement. + +--- + +## **Next Steps** + +### **Focus Areas for Next Sprint** + +- Enhance coding and problem-solving skills. +- Refine time management strategies. +- Explore more challenging exercises to deepen understanding of key concepts. +- Encourage frequent check-ins and cross-team collaboration to + address potential roadblocks early. + +### **New Skills to Learn** + +- Gain deeper proficiency in advanced coding techniques. +- Improve overall efficiency in managing workflows and deadlines. + +--- + +### **Final Note** + +The success of this project was a testament to the team's dedication, +openness, and collaborative spirit. By building on these strengths and + addressing the identified areas of improvement, future projects can be + even more effective and rewarding. diff --git a/solutions/FooBar_code.py b/solutions/FooBar_code.py new file mode 100644 index 000000000..12870214a --- /dev/null +++ b/solutions/FooBar_code.py @@ -0,0 +1,71 @@ +""" +Creates a sequence where numbers divisible by 4 return 'Foo', +numbers divisible by 6 return 'Bar', numbers divisible by both +return 'FooBar', and other numbers remain unchanged. + +Author: Rafaa Ali +Created date: 1.1.2025 +""" + + +def FooBar_code(n: int): + """This function, FooBar_code, creates a sequence that replaces numbers + based on their divisibility by 4 and 6. + + Parameters: + n (int): The last number in the list. + + Returns: + List: A list containing the sequence with 'Foo', 'Bar', 'FooBar' or numbers. + + Assertions: + n must be an integer; otherwise, an error will be raised. + + Example: + >>> FooBar_code(1) + [1] + >>> FooBar_code(0) + [] + >>> FooBar_code(-1) + [] + + Standard cases: + >>> FooBar_code(8) + [1, 2, 3, 'Foo', 5, 'Bar', 7, 'Foo'] + >>> FooBar_code(12) + [1, 2, 3, 'Foo', 5, 'Bar', 7, 'Foo', 9, 10, 11, 'FooBar'] + >>> FooBar_code(16) + [1, 2, 3, 'Foo', 5, 'Bar', 7, 'Foo', 9, 10, 11, 'FooBar', 13, 14, 15, 'Foo'] + + Defensive cases: + >>> FooBar_code("rafaa") + AssertionError: n must be an integer + >>> FooBar_code(5.5) + AssertionError: n must be an integer + >>> FooBar_code([1, 2, 3]) + AssertionError: n must be an integer + """ + # Assert an error when n is not an integer + assert isinstance(n, int), "n must be an integer" + if not isinstance: + assert TypeError + # Prepare an empty list for the result + result = [] + + # Loop for checking the sequence + for i in range(1, n + 1): + # Check if the number is divisible by both 4 and 6 + if i % 4 == 0 and i % 6 == 0: + result.append("FooBar") + # Check if the number is divisible by 4 + elif i % 4 == 0: + result.append("Foo") + # Check if the number is divisible by 6 + elif i % 6 == 0: + result.append("Bar") + # Otherwise, add the number to the list + else: + result.append(i) + + # Return the result's list + return result diff --git a/solutions/LowestCommonAncestor_in_BST.py b/solutions/LowestCommonAncestor_in_BST.py new file mode 100644 index 000000000..53bfd5068 --- /dev/null +++ b/solutions/LowestCommonAncestor_in_BST.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This module for Lowest_Common_Ancestor_in Binary Search Tree function. +Author: Safia Gibril +Created on: 2.1.2025 +""" + + +class TreeNode: + """Define class for TreeNode""" + + def __init__(self, value=0, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +def lowestCommonAncestor(root: "TreeNode", p: "TreeNode", q: "TreeNode") -> "TreeNode": + r""" + Finds the Lowest Common Ancestor (LCA) of two nodes in a Binary Search Tree. + + In a Binary Search Tree, the Lowest Common Ancestor between two nodes p and q is the lowest node + such that both p and q are descendants of this node (a node can be a descendant of itself). + + Args: + root (TreeNode): The root node of the Binary Search Tree. + p (TreeNode): The first node in the tree. + q (TreeNode): The second node in the tree. + + Returns: + TreeNode: The Lowest Common Ancestor of nodes p and q. + + Raises: + ValueError: If any of the inputs (root, p, or q) is None. + + Notes: + - If both p and q are smaller than the current node, the LCA is in the left subtree. + - If both p and q are larger than the current node, the LCA is in the right subtree. + - Otherwise, the current node is the LCA. + + Examples: + Example 1: + Input: root = [5,3,8,1,4,7,9,null,2], p = TreeNode(3), q = TreeNode(8) + Output: TreeNode(5) + + Example 2: + Input: root = [5,3,8,1,4,7,9,null,2], p = TreeNode(3), q = TreeNode(4) + Output: TreeNode(3) + + Example: + Tree Structure: + 5 + / \\ + 3 8 + / \ / \\ + 1 4 7 9 + \\ + 2 + + Example 1: + Input: root = [5,3,8,1,4,7,9,null,2], p = 3, q = 8 + Output: 5 + + Example 2: + Input: root = [5,3,8,1,4,7,9,null,2], p = 3, q = 4 + Output: 3 + """ + + # Check if root, p, or q are None and raise ValueError if so + if root is None or p is None or q is None: + raise ValueError("None of the inputs (root, p, q) can be None.") + + candidate = root + # this tree satisfies BST properties + while candidate: + if p.value > candidate.value and q.value > candidate.value: + candidate = candidate.right + elif p.value < candidate.value and q.value < candidate.value: + candidate = candidate.left + else: + return candidate diff --git a/solutions/Roman_to_integer.py b/solutions/Roman_to_integer.py new file mode 100644 index 000000000..39086037c --- /dev/null +++ b/solutions/Roman_to_integer.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Module for converting Roman numerals to integers. + +Provides the RomanConverter class with a method to convert valid Roman numeral strings +into their integer representation. + +Author: Safia Gibril +Created on: 2.1.2025 +""" + + +class RomanConverter: + """This class identify Roman Converter.""" + + def roman_to_integer(self, roman_str: str) -> int: + """ + Convert a Roman numeral string to an integer. + + Args: + roman_str (str): The Roman numeral string to convert. + + Returns: + int: The integer representation of the Roman numeral. + + Exceptions: + If `roman_str` is not a string or is None, return 0. + + + """ + if not isinstance(roman_str, str) or roman_str is None: + return 0 + + roman = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} + result = 0 + + for idx, char in enumerate(roman_str): + if idx + 1 < len(roman_str) and roman[char] < roman[roman_str[idx + 1]]: + result -= roman[char] + else: + result += roman[char] + + return result diff --git a/solutions/__init__.py b/solutions/__init__.py index 8b1378917..e69de29bb 100644 --- a/solutions/__init__.py +++ b/solutions/__init__.py @@ -1 +0,0 @@ - diff --git a/solutions/armstrong_checker.py b/solutions/armstrong_checker.py new file mode 100644 index 000000000..d203b79d5 --- /dev/null +++ b/solutions/armstrong_checker.py @@ -0,0 +1,38 @@ +"""Module checks if a number is an Armstrong number. +Created on 2025-01-02 +Author: Ahmed +""" + + +def armstrong_checker(number: int) -> str: + """ + Checks if a given positive integer is an Armstrong number. + + Parameters: + number (int): The number to check (must be > 0). + + Returns: + str: + - "True" if the number is an Armstrong number. + - "False" if the number is not an Armstrong number. + + Raises: + ValueError: If the input is not a positive integer. + + Examples: + >>> armstrong_checker(153) + 'True' + >>> armstrong_checker(123) + 'False' + >>> armstrong_checker(9474) + 'True' + """ + if not isinstance(number, int) or number <= 0: + raise ValueError("Invalid input: number must be a positive integer.") + + # Calculate the sum of the nth power of each digit in the number. + digits = [int(d) for d in str(number)] + n = len(digits) + armstrong_sum = sum(d**n for d in digits) + + return "True" if armstrong_sum == number else "False" diff --git a/solutions/copy_from_file_to_file.py b/solutions/copy_from_file_to_file.py new file mode 100644 index 000000000..65d9f2bcd --- /dev/null +++ b/solutions/copy_from_file_to_file.py @@ -0,0 +1,58 @@ +""" +This module copies content from one file to another. + +Module contents: + - `source_file` (.txt): the file to read content from. + - `destination_file` (.txt): the file to write content to. + +Created on 1 Jan 2025 "Happy New Year!" +@author: Saja Abusafia +""" + +import os + + +def copy_file_to_file(source_file: str, destination_file: str) -> None: + """ + Copies content from a source file to a destination file. + + Parameters: + source_file (str): The file to read the content from. + destination_file (str): The file to write the content to. + + Returns: + None:Method performs the copying operation but doesn't return anything. + + Raises: + AssertionError: If the source file does not exist. + AssertionError: If the source file is not a .txt file. + AssertionError: If the destination file is not a .txt file. + + + >>> copy_file_to_file("solutions/source.txt", "solutions/destination.txt") + + >>> copy_file_to_file("solutions/empty.txt", "solutions/destination.txt") + + >>> copy_file_to_file("solutions/destination.txt", "solutions/empty.txt") + + """ + # Defensive checks + assert os.path.exists(source_file), ( + f"Error: Source file '{source_file}' doesn't exist." + ) + assert source_file.endswith(".txt"), ( + f"Error: Source file '{source_file}' is not a .txt file." + ) + assert destination_file.endswith(".txt"), ( + f"Error: Destination file '{destination_file}' is not a .txt file." + ) + + # Copy operation: Open the source file in read mode + with open(source_file, "r") as file_from: + # Read the entire content of the source file + file_content = file_from.read() + + # Open the destination file in write mode + with open(destination_file, "w") as file_to: + # Write the content read from the source file into the destination file + file_to.write(file_content) diff --git a/solutions/count_characters.py b/solutions/count_characters.py new file mode 100644 index 000000000..6ea62bebf --- /dev/null +++ b/solutions/count_characters.py @@ -0,0 +1,67 @@ +""" +Creates a function that counts the number of vowels (a, e, i, o, u) in a given string. +The function is case-insensitive, meaning both uppercase and lowercase vowels are counted. + +Author: Rafaa Ali +Created date: 3.1.2025 +""" + + +def count_characters(string: str) -> int: + """This function, `count_vowels`, counts the number of vowels in a given string, + ignoring case sensitivity. + + Parameters: + string (str): The input string to count vowels characters + + Returns: + int: The total number of vowels in the string + + assertion: + string must be string type otherwise error + + example: + Edge cases: + >>> count_characters("") + 0 + >>> count_characters("krm") + 0 + >>> count_characters(" ") + 0 + + Standard cases: + >>> count_characters lowercase("hello") + 2 + >>> count_characters uppercase("RAFF") + 1 + >>>count_characters mixed case("aEiOu") + 5 + + Defensive cases: + >>> count_characters(123) + AssertionError: Input must be string + >>>count_characters(["hello"]) + AssertionError: Input must be string + >>> count_characters(None) + AssertionError: Input must be string + """ + # assert an error when input is not string + assert isinstance(string, str), "Input must be string" + + # Convert string to lowercase for case-insensitive counting + string = string.lower() + + # Define vowels to check against + vowels = "aeiou" + + # Initialize counter for vowels + count = 0 + + # Loop through each character in the string + for char in string: + # If character is a vowel, increment counter + if char in vowels: + count += 1 + + # Return the total count of vowels + return count diff --git a/solutions/draw_triangle.py b/solutions/draw_triangle.py new file mode 100644 index 000000000..5cfbc1d36 --- /dev/null +++ b/solutions/draw_triangle.py @@ -0,0 +1,52 @@ +""" +module'draw': it prints a triangle pattern made of stars (*). + +the user enters a number and get a triangle of stars in descending order. + +Author: Mohamed Tilal +Created date: 6.1.2025 +""" +# the function + + +def draw_triangle(n: int) -> None: + """ + Prints a descending triangle pattern of stars (*). + + + parameters: + n (int): The number of stars in the first row. + Must be a positive integer. + + Returns: + None: The function prints the pattern but does not return any value. + + + Raises: + assertionError: if 'n' is not an integer + assertionError: if 'n' is less than one + + + Examples: + >>> draw_triangle(5) + ***** + **** + *** + ** + * + + >>> draw_triangle(3) + *** + ** + * + + """ + + assert isinstance(n, int), " parameter 'n' must be an integer" + + if n < 1: + raise ValueError("parameter 'n' must be greater than or equal to 1.") + + for a in range(n, 0, -1): + """ creates a sequence from n down to 1""" + print("*" * a) diff --git a/solutions/empty.txt b/solutions/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/solutions/even_number_count.py b/solutions/even_number_count.py new file mode 100644 index 000000000..bf5de9905 --- /dev/null +++ b/solutions/even_number_count.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A function that counts the number of even numbers in a countdown from n to 1 + +Module contents: + - even_number_count: gen number for even numbers in the list. + +Created on Monday/ 30/ December/ 2024 +@author: Mayar Ali. +""" + + +def even_number_count(number: list) -> int: + """Returns the number of even numbers on the list. + + Parameters: + int: list, the input list to process + + return - > int: indicating the number of even number in the list + + Raises: + AssertionError: + + Examples: + >>> even_number_count ([1]) + 0 + >>> even_number_count ([2,1]) + 1 + >>> even_number_count ([3,2,1]) + 1 + """ + assert isinstance(number, list) + for num in number: + assert isinstance(num, int) + count = 0 + + for num in number: + if num % 2 == 0: + count += 1 + return count + + +if __name__ == "__main__": + print(even_number_count([1, 2, 3, 4, 5])) diff --git a/solutions/factorial.py b/solutions/factorial.py new file mode 100644 index 000000000..2009bfee1 --- /dev/null +++ b/solutions/factorial.py @@ -0,0 +1,53 @@ +""" +This module provides a function to calculate the factorial of a number recursively. + +The factorial function is defined only for non-negative integers. + +Created on 01/01/2025 + +@author: Safa Ishag + +Challenge is from the Leetcode website. + +""" + + +def factorial(n: int) -> int: + """ + Calculate the factorial of a given non-negative integer. + + Parameters: + n (int): The number whose factorial is to be calculated. Must be >= 0. + + Returns: + int: The factorial of the input number. + + Raises: + ValueError: If `n` is negative. + + Doctests: + >>> factorial(0) + 1 + >>> factorial(1) + 1 + >>> factorial(5) + 120 + >>> factorial(-1) + Traceback (most recent call last): + ... + ValueError: Input must be a non-negative integer. + """ + # Defensive assertion: Input must be a non-negative integer + if not isinstance(n, int): + raise TypeError("Input must be an integer.") + if n < 0: + raise ValueError("Input must be a non-negative integer.") + if n in (0, 1): + return 1 + return n * factorial(n - 1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/solutions/factorial_calculator.py b/solutions/factorial_calculator.py new file mode 100644 index 000000000..e0860ff34 --- /dev/null +++ b/solutions/factorial_calculator.py @@ -0,0 +1,52 @@ +""" +factorial calculator, +This module provides a function to calculate the factorial of a given non-negative integer. + +Mathematical Definition: +n! = n * (n - 1) * (n - 2) * ... * 1 + +By convention, 0! = 1. + +""" +# Author: AYHAM +# Date: 6/jan/2025 + + +def factorial(n: int) -> int: + """ + Parameters: + n(int): The number to calculate the factorial of. it should be a positive integer only + + Returns: + int: the factorial of the given number + + Raises: + ValueError -> If the input is None or a Negative integer + TypeError -> if the input is not an integer + + Examples: + + >>> factorial(5) + 120 + >>> factorial(0) + 1 + >>> factorial(3) + 6 + + """ + + if n is None: + raise ValueError("Input cannot be None") + + if not isinstance(n, int): + raise TypeError("Input must be an integer") + + if n < 0: + raise ValueError("Factorial is not defined for negative numbers") + + # initialize the result variable + fact = 1 + # a loop to compute the factorial + for i in range(1, n + 1): + fact *= i + return fact diff --git a/solutions/fuel_gauge.py b/solutions/fuel_gauge.py new file mode 100644 index 000000000..b78b93b65 --- /dev/null +++ b/solutions/fuel_gauge.py @@ -0,0 +1,41 @@ +"""module calculates the percentage of fuel in a tank and returns a status indicator. +Created on Sunday/29/December/2024 +Author: Ahmed Elhassan""" + + +def fuel_gauge(fuel_amount: int, tank_capacity: int) -> str: + """ + Calculates the percentage of fuel in a tank and returns a status indicator. + + Parameters: + fuel_amount (int): Fuel in the tank (must be ≤ tank_capacity). + tank_capacity (int): Tank capacity (must be > 0). + + Returns: + str: + - "F" if percentage >= 90. + - "E" if percentage <= 10. + - "M" for all other cases. + - "Invalid input" for invalid inputs. + + Raises: + AssertionError: If tank_capacity <= 0 or fuel_amount > tank_capacity. + + Examples: + >>> fuel_gauge(90, 100) + 'F' + >>> fuel_gauge(10, 100) + 'E' + >>> fuel_gauge(50, 100) + 'M' + """ + if tank_capacity <= 0 or fuel_amount > tank_capacity: + raise AssertionError("Invalid tank capacity or fuel amount") + + percentage = (fuel_amount / tank_capacity) * 100 + + if percentage >= 90: + return "F" + if percentage <= 10: + return "E" + return "M" diff --git a/solutions/game_greeting.py b/solutions/game_greeting.py new file mode 100644 index 000000000..05034de45 --- /dev/null +++ b/solutions/game_greeting.py @@ -0,0 +1,35 @@ +""" +Module: generate a game welcome message + +the Module provides a function that generates a welcome message for a player joining a game. + +Author: Mohamed Tilal +Created date: 6.1.2025 +""" +# the function: + + +def game_greeting(name: str) -> str: + """ + the player inters his name as an input and in return he gets + a greeting welcome massage. + + + parameters: + name (string)- The player's name. can be empty string or special characters. + + Returns: + string- A formatted Welcome message in the form: + "Welcome, [name] to the game" + + Raises: + assertionError : if name is not a string + + Example: + >>> game_greeting({Mohamed}) + "Welcome, Mohamed to the game" + + """ + assert isinstance(name, str), "Name should be string" + + return f"Welcome, {name} to the game" diff --git a/solutions/harmonic_sequence.py b/solutions/harmonic_sequence.py new file mode 100644 index 000000000..8e05c2656 --- /dev/null +++ b/solutions/harmonic_sequence.py @@ -0,0 +1,82 @@ +"""Creates harmonic sequence, based on formula: A(n): 1/ (a +(n-1) *d) +given a, d and n are integers. and returns +the list containing the harmonic sequence. + +Created on 2024-12-30 +Author: Omnia Mustafa - Abdulgadir +""" + + +def harmonic_sequence(a: int, d: int, n: int) -> list[float]: + """ + This function ,`harmonic_sequence`, creates a harmonic sequence, + based on formula: A(n): 1/ (a +(n-1) *d) + + Parameters: + a (int): First term + d (int): Common difference + n (int): Number of terms to generate + + Returns: + List [float]: List containing the harmonic sequence + + Assertions: + - a must be integer + - d must be integer + - n must be integer + + Examples: + Edge cases: + >>> harmonic_sequence(1, 1, 1) + [1.0] + >>> harmonic_sequence(1, 1, 0) + [] + >>> harmonic_sequence(0, 0, 5) + [0, 0, 0, 0, 0] + >>> harmonic_sequence(1, 1, -1) + [] + >>> harmonic_sequence(1, 1, 5) + [1.0, 0.5, 0.3333333333333333, 0.25, 0.2] + + Standard cases: + >>> harmonic_sequence(2, 2, 3) + [0.5, 0.25, 0.16666666666666666] + >>> harmonic_sequence(4, 3, 10) + [0.25, + 0.14285714285714285, + 0.1, + 0.07692307692307693, + 0.0625, + 0.05263157894736842, + 0.045454545454545456, + 0.04, + 0.03571428571428571, + 0.03225806451612903,] + + Defensive cases: + >>> harmonic_sequence("1", 1, 1) + AssertionError: A must be integer + >>> harmonic_sequence(5, "Omnia", 10) + AssertionError: D must be integer + >>> harmonic_sequence(1, 1, [1, 2, 3]) + AssertionError: N must be integer + """ + # Assert an error if any input is not integer + assert isinstance(a, int), "A must be integer" + assert isinstance(d, int), "D must be integer" + assert isinstance(n, int), "N must be integer" + if not isinstance: + assert TypeError + # Create an empty list to save the sequence + sequence = [] + # If n is less than or equal to 0, return empty list + if n <= 0: + return sequence + # If a and d are equal to 0, return a list of n zeros + if a == 0 and d == 0: + return [0] * n + # If all inputs are greater than 1, return the sequence + for i in range(1, n + 1): + term = 1 / (a + (i - 1) * d) + sequence.append(term) + return sequence diff --git a/solutions/is_anagram.py b/solutions/is_anagram.py new file mode 100644 index 000000000..f66888800 --- /dev/null +++ b/solutions/is_anagram.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Write a function that takes two strings as input and checks if they +are anagrams of each other. Anagrams are words or phrases made by +rearranging the letters of another, using all original letters +exactly once + +True if the two strings are anagrams, False otherwise + +Created on 2024-01-02 +Author: Reem Osama +""" + +from collections import Counter + + +def is_anagram(first_word: str, second_word: str) -> bool: + """ + Returns true or false if the two strings given are anagrams + of each other. + + Anagram is word or a phrase formed by rearranging the letters of another + word or phrase, using all the original letters exactly once + + "listen" -> "silent" + + Parameters: + first_word: The first string + second_word: The second string + + Returns -> bool: Either true or false of either the strings + are anagrams of each other or not. + + Raises: + AssertionError: if the parameters are not strings + + Examples: + >>> is_anagram("listen", "silent") + True + >>> is_anagram("rat", "car") + False + >>> is_anagram("evil", "vile") + True + """ + assert isinstance(first_word, str), "First word must be a string" + assert isinstance(second_word, str), "Second word must be a string" + + if len(first_word) != len(second_word): + return False + + # This is a time complexity of O(n log n) answer + # return sorted(first_word) == sorted(second_word) + + # This is a time complexity of O(n) answer + return Counter(first_word) == Counter(second_word) diff --git a/solutions/quadratic_sequence.py b/solutions/quadratic_sequence.py new file mode 100644 index 000000000..2dbdd69da --- /dev/null +++ b/solutions/quadratic_sequence.py @@ -0,0 +1,73 @@ +""" +The function is create quadratic sequence based on formula: A(n): a*n^2 + b*n + c +given a,b,c and n are integers.And returns the list containing the quadratic sequence. + +Created on 2024-12-30 +Author: Omnia Mustafa - Abdulgadir +""" + + +def quadratic_sequence(a, b, c, n): + """ + The function creates quadratic sequence, based on formula: A(n): a*n^2 + b*n + c + + Parameters: + a (int): Coefficient of n^2 + b (int): Coefficient of n + c (int): Constant term + n (int): Number of terms to generate + + Returns: + List: List containing the quadratic sequence + + Assertions: + - a must be integer + - b must be integer + - c must be integer + - n must be integer + + Examples: + Edge cases: + >>> quadratic_sequence(0, 0, 0, 0) + [] + >>> quadratic_sequence(5, 3, 2, 0) + [] + >>> quadratic_sequence(1, 1, 1, 1) + [3] + >>> quadratic_sequence(0, 5, 3, 2) + [8, 13] + >>> quadratic_sequence(5, 0, 3, 2) + [8, 23] + >>> quadratic_sequence(5, 3, 0, 2) + [8, 26] + Standard cases: + >>> quadratic_sequence(3, 3, 3, 3) + [9, 21, 39] + >>> quadratic_sequence(1, 2, 3, 4) + [6, 11, 18, 27] + Defensive cases: + >>> quadratic_sequence("1", 1, 1, 1) + AssertionError: A must be integer + >>> quadratic_sequence(5, "Omnia", 10, 6) + AssertionError: B must be integer + >>> quadratic_sequence(1, 5, [1, 2, 3], 8) + AssertionError: C must be integer + >>> quadratic_sequence(1, 1, 5, [1, 2, 3]) + AssertionError: N must be integer + + """ + # Assert error if inputs are not integers + assert isinstance(a, int), "A must be integer" + assert isinstance(b, int), "B must be integer" + assert isinstance(c, int), "C must be integer" + assert isinstance(n, int), "N must be integer" + # Create quadratic sequence + sequence = [] + # Return empty list if n is less than or equal to 0 + if n <= 0: + return sequence + # Create quadratic sequence + for i in range(1, n + 1): + term = (a * (i**2)) + (b * i) + c + sequence.append(term) + return sequence diff --git a/solutions/robot.py b/solutions/robot.py new file mode 100644 index 000000000..719a17ed4 --- /dev/null +++ b/solutions/robot.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +There is a robot on an m x n grid. The robot is initially located +at the top-left corner (i.e., grid[0][0]). The robot tries to move +to the bottom-right corner (i.e., grid[m - 1][n - 1]). The robot +can only move either down or right at any point in time. + +Given the two integers m and n, return the number of possible +unique paths that the robot can take to reach the bottom-right +corner. + +Constraints: The test cases are generated so that the answer will +be less than or equal to 2 * 109. + +Created on 2024-01-01 +Author: Reem Osama +""" + + +def robot_move(cell, rows, cols, memo) -> int: + """ + This is a helper function that determines the number of unique paths by + using dynamic programming and memoization + + Parameters: + cell: a tuple made of two elements refers to the current location + inside the grid - immutable, can be used as a key for dicts + rows: The number of rows in the grid. + cols: The number of columns in the grid. + memo: a dictionary to save answers for memoization + + Returns -> The number of unique paths from the top-left corner + to the bottom-right corner. + """ + # if the cell is stored in the memo dict we return it + if memo.get(cell): + return memo.get(cell) + # if the cell is directly perpendicular to target, there's only one road + if cell[1] >= cols - 1 or cell[0] >= rows - 1: + return 1 + # otherwise, there's two roads to take, the answer is the sum of both + memo[cell] = robot_move((cell[0] + 1, cell[1]), rows, cols, memo) + robot_move( + (cell[0], cell[1] + 1), rows, cols, memo + ) + # we save the answer in the memo for future referance and return it + return memo[cell] + + +def robot(rows: int, cols: int) -> int: + """ + Returns the number of possible unique routes to reach the + most bottom-right corner + + Parameters: + rows: The number of rows in the grid. + cols: The number of columns in the grid. + + Returns -> The number of unique paths from the top-left corner + to the bottom-right corner. + + Raises: + AssertionError: if rows and cols are not integers or less than 1 + + Examples: + >>> robot(3, 7) + 28 + >>> robot(4, 3) + 10 + >>> robot(23, 12) + 193536720 + """ + # assert the values of rows and clos and make sure they are ints and + # greater than 1 + assert isinstance(rows, int), "Rows variables has to be an integer" + if rows <= 0: + raise ValueError("The 'rows' variable must be greater than 0.") + assert isinstance(cols, int), "Cols variable has to be an integer" + if cols <= 0: + raise ValueError("The 'cols' variables must be greater than 0.") + + # use the helper function robot_move to determine the number of paths + memo = {} + return robot_move((0, 0), rows, cols, memo) # (0, 0) is the initial spot diff --git a/solutions/source.txt b/solutions/source.txt new file mode 100644 index 000000000..8d5189012 --- /dev/null +++ b/solutions/source.txt @@ -0,0 +1 @@ +This content is written inside the Source file .txt diff --git a/solutions/substitution_cipher.py b/solutions/substitution_cipher.py new file mode 100644 index 000000000..ca8f0d43b --- /dev/null +++ b/solutions/substitution_cipher.py @@ -0,0 +1,57 @@ +""" +Substitution Cipher +this code provides a function to encrypt text using a caesar cipher, +shifting lower and uppercase letter by a fixed value of 3 (as per the challenge) +""" + +# Author: AYHAM, 1/1/2025 + +import string + + +def cipher(text: str) -> str: + """ + Parameters: + text (str): The text to be encrypted. + Assumes input consists of alphabetic chars, optionally mixed with non alphabetic chars + those would remain unchanged. + + + Returns: + str: The encrypted text. only alphabetic characters shifted by 3 positions + and non alphabetical values unchanged. + + Raises: + ValueError: If input is not a string + + >>> cipher("abc") + 'def' + >>> cipher("omnia") + 'rpqld' + >>> cipher("Hello!") + 'Khoor!' + + """ + + shift_value = 3 + + # Validate that the input is a string. + if not isinstance(text, str): + raise ValueError("Input text must be a string.") + + # Define lowercase and uppercase alphabets for shifting. + alphabets = [string.ascii_lowercase, string.ascii_uppercase] + + def shift_alphabet(alphabet: str) -> str: + return alphabet[shift_value:] + alphabet[:shift_value] + + # Generate shifted versions of the alphabets. + shifted_alphabets = tuple(map(shift_alphabet, alphabets)) + + # Combine original and shifted alphabets into strings for translation. + final_alphabet = "".join(alphabets) + final_shifted_alphabet = "".join(shifted_alphabets) + + # Create a translation table to map characters to their shifted counterparts. + table = str.maketrans(final_alphabet, final_shifted_alphabet) + return text.translate(table) diff --git a/solutions/summation.py b/solutions/summation.py new file mode 100644 index 000000000..f2a3c4c11 --- /dev/null +++ b/solutions/summation.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for determining whether a given number is a sum of a defined number. + +Module contents: + - sum: checks if a given integer is a sum of those given numbers. + +Created on 01/01/2025 + +@author: Safa Ishag + +Challenge is from the Leetcode website. + +Defining the sum of a given number: + +""" + + +def sum(*args: float) -> float: + """ + Return the sum of all input numbers. + + Parameters: + *args (int or float): The number to check and is the variable. + + Returns: + int or float: The sum of all input numbers. + + Raises: + TypeError: If any input is not a number. + + Example: + >>> sum(1, 2, 3, 4, 5) + 15 + >>> sum(3, 6, 2, 1, 8) + 20 + >>> sum(4, 9, 7, 2, 5) + 27 + + """ + # Defensive assertion: Ensure all inputs are numbers + if not all(isinstance(arg, (int, float)) for arg in args): + raise TypeError("All inputs must be numeric (int or float).") + + # Iterate through the input arguments and calculate the sum + + total = 0 + + for i in args: + total += i + + return total diff --git a/solutions/test_5_words.txt b/solutions/test_5_words.txt new file mode 100644 index 000000000..9f4b6d8bf --- /dev/null +++ b/solutions/test_5_words.txt @@ -0,0 +1 @@ +This is a test file diff --git a/solutions/test_empty.txt b/solutions/test_empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/solutions/test_multiple.txt b/solutions/test_multiple.txt new file mode 100644 index 000000000..2c07b4bce --- /dev/null +++ b/solutions/test_multiple.txt @@ -0,0 +1,3 @@ +This is a test +when the file +has multiple lines diff --git a/solutions/tests/__init__.py b/solutions/tests/__init__.py index 8b1378917..e69de29bb 100644 --- a/solutions/tests/__init__.py +++ b/solutions/tests/__init__.py @@ -1 +0,0 @@ - diff --git a/solutions/tests/test_FooBar_code.py b/solutions/tests/test_FooBar_code.py new file mode 100644 index 000000000..f066f1c22 --- /dev/null +++ b/solutions/tests/test_FooBar_code.py @@ -0,0 +1,73 @@ +"""Test module for FooBar_code function""" + +import unittest + +from solutions.FooBar_code import FooBar_code + + +class TestFooBar(unittest.TestCase): + """This class tests the FooBar_code function""" + + # Edge cases: + def test_int1(self): + """Check if the input is 1""" + self.assertEqual(FooBar_code(1), [1]) + + def test_int0(self): + """Check if the input is 0""" + self.assertEqual(FooBar_code(0), []) + + def test_negative_int(self): + """Check if the input is negative""" + self.assertEqual(FooBar_code(-1), []) + + # Standard cases: + def test_int8(self): + """Check if the input is 8""" + self.assertEqual(FooBar_code(8), [1, 2, 3, "Foo", 5, "Bar", 7, "Foo"]) + + def test_int12(self): + """Check if the input is 12""" + self.assertEqual( + FooBar_code(12), [1, 2, 3, "Foo", 5, "Bar", 7, "Foo", 9, 10, 11, "FooBar"] + ) + + def test_is_int(self): + """Check if the input is a large number""" + self.assertEqual( + FooBar_code(16), + [ + 1, + 2, + 3, + "Foo", + 5, + "Bar", + 7, + "Foo", + 9, + 10, + 11, + "FooBar", + 13, + 14, + 15, + "Foo", + ], + ) + + # Defensive cases: + def test_string_input(self): + """It should raise AssertionError for string input""" + with self.assertRaises(AssertionError): + FooBar_code("rafaa") + + def test_is_float_input(self): + """It should raise AssertionError for float input""" + with self.assertRaises(AssertionError): + FooBar_code(5.5) + + def test_is_list_input(self): + """It should raise AssertionError for list input""" + with self.assertRaises(AssertionError): + FooBar_code([1, 2, 3]) diff --git a/solutions/tests/test_LowestCommonAncestor_in_BST.py b/solutions/tests/test_LowestCommonAncestor_in_BST.py new file mode 100644 index 000000000..c04aa676d --- /dev/null +++ b/solutions/tests/test_LowestCommonAncestor_in_BST.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for Lowest Common Ancestor in Binary Search Tree function. +Author: Safia Gibril +Created on: 2.1.2025 +""" + +import unittest + +from solutions.LowestCommonAncestor_in_BST import lowestCommonAncestor + + +class TreeNode: + """Define class for TreeNode""" + + def __init__(self, value=0, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +class TestLowestCommonAncestor(unittest.TestCase): + """Test the Lowest Common Ancestor in Binary Search Tree function. + + Binary Search Tree structure: + + 5 + / \\ + 3 8 + / \ / \\ + 1 4 7 9 + \\ + 2 + """ + + def setUp(self): + """Construct Binary Search Tree from the example.""" + self.root = TreeNode(5) + self.root.left = TreeNode(3) + self.root.right = TreeNode(8) + self.root.left.left = TreeNode(1) + self.root.left.right = TreeNode(4) + self.root.right.left = TreeNode(7) + self.root.right.right = TreeNode(9) + self.root.left.left.right = TreeNode(2) + + # Basic cases + def test_level_1(self): + """Test LCA of node 3 and node 8 on the same level""" + p = self.root.left + q = self.root.right + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 5) + + def test_different_levels_case_1(self): + """Test LCA for node 2 and node 9 on different levels""" + p = self.root.left.left.right + q = self.root.right.right + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 5) + + def test_different_levels_case_2(self): + """Test LCA for node 1 and node 2 on different levels""" + p = self.root.left.left.right + q = self.root.left.left + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 1) + + def test_different_levels_case_3(self): + """Test LCA for node 3 and node 7 on different levels""" + p = self.root.left.left + q = self.root.right.left + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 5) + + def test_different_levels_case_4(self): + """Test LCA for node 7 and node 9 on different levels""" + p = self.root.right.left + q = self.root.right.right + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 8) + + def test_different_levels_root_2(self): + """Test LCA for root 5 and node 7 on different levels""" + p = self.root + q = self.root.right.left + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 5) + + # Edge cases + def test_ancestor_is_the_node_itself(self): + """Test LCA where the ancestor is one of the nodes itself (3, 4)""" + p = self.root.left + q = self.root.left.right + candidate = lowestCommonAncestor(self.root, p, q) + self.assertEqual(candidate.value, 3) + + # AssertionError test cases + def test_assertion_error_root_none(self): + """Test when root is None""" + with self.assertRaises(ValueError): + lowestCommonAncestor(None, self.root.left, self.root.right) + + def test_assertion_error_p_none(self): + """Test when p is None""" + with self.assertRaises(ValueError): + lowestCommonAncestor(self.root, None, self.root.right) + + def test_assertion_error_q_none(self): + """Test when q is None""" + with self.assertRaises(ValueError): + lowestCommonAncestor(self.root, self.root.left, None) diff --git a/solutions/tests/test_Roman_to_integer.py b/solutions/tests/test_Roman_to_integer.py new file mode 100644 index 000000000..623ad4152 --- /dev/null +++ b/solutions/tests/test_Roman_to_integer.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for Roman to integer function. + +Author: Safia Gibril +Created on: 2.1.2025 +""" + +import unittest + +from solutions.Roman_to_integer import RomanConverter + + +class TestRomanToInteger(unittest.TestCase): + """ + Unittest class to test the roman_to_integer function. + """ + + def test_case_0(self): + """Test conversion of single Roman (X) to numeral 10""" + self.assertEqual(RomanConverter().roman_to_integer("X"), 10) + + def test_case_1(self): + """Test conversion of Roman numeral to 1000 (M)""" + self.assertEqual(RomanConverter().roman_to_integer("M"), 1000) + + def test_case_2(self): + """Test conversion of Roman (XC) to numeral 90""" + self.assertEqual(RomanConverter().roman_to_integer("XC"), 90) + + def test_case_3(self): + """Test conversion of Roman (IX) numeral to 9""" + self.assertEqual(RomanConverter().roman_to_integer("IX"), 9) + + def test_case_4(self): + """Test conversion of Roman (LXXXVII) numeral to 87""" + self.assertEqual(RomanConverter().roman_to_integer("LXXXVII"), 87) + + def test_case_5(self): + """Test conversion of Roman numeral to 3999 (MMMCMXCIX)""" + self.assertEqual(RomanConverter().roman_to_integer("MMMCMXCIX"), 3999) + + def test_case_6(self): + """Test non-string input, expect 0""" + self.assertEqual(RomanConverter().roman_to_integer(123), 0) + + def test_case_7(self): + """Test non-string input (None), expect 0""" + self.assertEqual(RomanConverter().roman_to_integer(None), 0) + + def test_case_8(self): + """Test empty string input, expect 0""" + self.assertEqual(RomanConverter().roman_to_integer(""), 0) diff --git a/solutions/tests/test_armstrong_checker.py b/solutions/tests/test_armstrong_checker.py new file mode 100644 index 000000000..82ec5b0d4 --- /dev/null +++ b/solutions/tests/test_armstrong_checker.py @@ -0,0 +1,43 @@ +""" +Unit tests for the armstrong_checker module. +""" + +import unittest + +from ..armstrong_checker import armstrong_checker + + +class TestArmstrongChecker(unittest.TestCase): + """Test cases for the armstrong_checker function.""" + + def test_valid_armstrong_numbers(self): + """Test cases where the number is an Armstrong number.""" + self.assertEqual(armstrong_checker(153), "True") + + def test_non_armstrong_numbers(self): + """Test cases where the number is not an Armstrong number.""" + self.assertEqual(armstrong_checker(123), "False") + + def test_invalid_input_negative_number(self): + """Test case for a negative number input.""" + with self.assertRaises(ValueError): + armstrong_checker(-153) + + def test_invalid_input_string(self): + """Test case for a string input.""" + with self.assertRaises(ValueError): + armstrong_checker("153") + + def test_invalid_input_float(self): + """Test case for a float input.""" + with self.assertRaises(ValueError): + armstrong_checker(0.5) + + def test_armstrong_number_single_digit(self): + """Test a single-digit Armstrong number.""" + self.assertEqual(armstrong_checker(5), "True") + + def test_invalid_input_zero(self): + """Test zero as input (invalid).""" + with self.assertRaises(ValueError): + armstrong_checker(0) diff --git a/solutions/tests/test_copy_from_file_to_file.py b/solutions/tests/test_copy_from_file_to_file.py new file mode 100644 index 000000000..f17a00ebb --- /dev/null +++ b/solutions/tests/test_copy_from_file_to_file.py @@ -0,0 +1,85 @@ +""" +This function copies content from one file to another. + +Parameters: + source_file (str): The file to read the content from + destination_file (str): The file to write the content to + +Returns: + None: The function performs the copying operation but does not return anything + +Raises: + AssertionError: If the source file does not exist + AssertionError: If the source or destination file is not a .txt file +""" + +import unittest + +from ..copy_from_file_to_file import copy_file_to_file + + +class TestCopyFromFileToFile(unittest.TestCase): + """Test various scenarios for copying content from one file to another""" + + def test_des_not_exist(self): + """Test when the destination file does not exist""" + source_file = "solutions/source.txt" + destination_file = "solutions/destination.txt" + + # Call the function to copy content from source to destination + copy_file_to_file(source_file, destination_file) + + # Open both files and check if the content matches + with ( + open("solutions/source.txt", "r") as source, + open("solutions/destination.txt", "r") as destination, + ): + # Assert that content of the source is identical to the destination + self.assertEqual(source.read(), destination.read()) + + def test_copy_to_file(self): + """Test when the destination file already exists""" + source_file = "solutions/source.txt" + destination_file = "solutions/destination.txt" + + # Call the function to copy content from source to destination + copy_file_to_file(source_file, destination_file) + + # Open both files and compare their content + with ( + open("solutions/source.txt", "r") as source, + open("solutions/destination.txt", "r") as destination, + ): + # Assert that content of the source is identical to the destination + self.assertEqual(source.read(), destination.read()) + + def test_not_a_txt_file(self): + """It raises an assertion error if the source file is not a .txt file""" + with self.assertRaises(AssertionError): + # Call the function with a source file that is not a .txt file + copy_file_to_file("solutions/Source.", "solutions/destination.txt") + + def test_source_file_empty(self): + """Test when the source file is empty""" + source_file = "solutions/empty.txt" + destination_file = "solutions/destination.txt" + + # Copy content from an empty source file to the destination file + copy_file_to_file(source_file, destination_file) + + # Open both files and compare their content + with ( + open("solutions/empty.txt", "r") as source, + open("solutions/destination.txt", "r") as destination, + ): + # Assert that both the source and destination are empty (no content) + self.assertEqual(source.read(), destination.read()) + + def test_src_not_exist(self): + """Test when the source file does not exist""" + source_file = "solutions/not_exist.txt" + destination_file = "solutions/destination.txt" + + # Check if it raises an assertion error when the source does not exist + with self.assertRaises(AssertionError): + copy_file_to_file(source_file, destination_file) diff --git a/solutions/tests/test_count_characters.py b/solutions/tests/test_count_characters.py new file mode 100644 index 000000000..104e4715c --- /dev/null +++ b/solutions/tests/test_count_characters.py @@ -0,0 +1,66 @@ +""" +test_count_characters.py +this module contains unit tests for the 'count_characters' +function + +""" + +import unittest + +from ..count_characters import count_characters + + +class TestCountCharacters(unittest.TestCase): + # Edge cases: + def test_empty_string(self): + """check if the input is empty string""" + self.assertEqual(count_characters(""), 0) + + def test_no_vowels(self): + """check if string has no vowels""" + self.assertEqual(count_characters("krm"), 0) + + """check if string is just whitespace""" + self.assertEqual(count_characters(" "), 0) + + # Standard cases: + def test_lowercase_word(self): + """check if handles lowercase correctly""" + self.assertEqual(count_characters("hello"), 2) + + def test_uppercase_word(self): + """check if handles uppercase correctly""" + self.assertEqual(count_characters("RAFF"), 1) + + def test_mixed_case(self): + """check if handles mixed case correctly""" + self.assertEqual(count_characters("aEiOu"), 5) + + def test_sentence(self): + """check if handles full sentence correctly""" + self.assertEqual(count_characters("Hello World"), 3) + + def test_all_vowels(self): + """check if counts all vowel types correctly""" + self.assertEqual(count_characters("AeIoU"), 5) + + # defensive cases + def test_number_input(self): + """It should raise AssertionError for integer input""" + with self.assertRaises(AssertionError): + count_characters(123) + + def test_list_input(self): + """It should raise AssertionError for list input""" + with self.assertRaises(AssertionError): + count_characters(["hello"]) + + def test_none_input(self): + """It should raise AssertionError for None input""" + with self.assertRaises(AssertionError): + count_characters(None) + + def test_float_input(self): + """It should raise AssertionError for float input""" + with self.assertRaises(AssertionError): + count_characters(3.14) diff --git a/solutions/tests/test_draw_triangle.py b/solutions/tests/test_draw_triangle.py new file mode 100644 index 000000000..d9c19f578 --- /dev/null +++ b/solutions/tests/test_draw_triangle.py @@ -0,0 +1,57 @@ +""" +test_draw_triangle.py + +this module contains unit tests for the 'draw_triangle' function. + +""" + +import unittest +from io import StringIO +import sys +from ..draw_triangle import draw_triangle + + +class TestDrawTriangle(unittest.TestCase): + """test the draw triangle function""" + + def setUp(self): + """set up for capturing printed output""" + self.captured_output = StringIO() + sys.stdout = self.captured_output + + def tearDown(self): + """reset standard output""" + sys.stdout = sys.__stdout__ + + def test_valid_input(self): + """test the function with valid input""" + draw_triangle(4) + self.assertEqual(self.captured_output.getvalue(), "****\n***\n**\n*\n") + + def test_large_input(self): + """test the function with a large input""" + draw_triangle(7) + self.assertEqual( + self.captured_output.getvalue(), + "*******\n******\n*****\n****\n***\n**\n*\n", + ) + + def test_non_integer_input(self): + """test the function with a non integer input""" + with self.assertRaises(AssertionError): + draw_triangle("aa") + + def test_negative_input(self): + """test the function with a negative input""" + with self.assertRaises(ValueError): + draw_triangle(-3) + + def test_zero_input(self): + """test the function with zero as the input""" + with self.assertRaises(ValueError): + draw_triangle(0) + + def test_small_input(self): + """test the function with the smallest input""" + draw_triangle(1) + self.assertEqual(self.captured_output.getvalue(), "*\n") diff --git a/solutions/tests/test_factorial.py b/solutions/tests/test_factorial.py new file mode 100644 index 000000000..264ad2c1f --- /dev/null +++ b/solutions/tests/test_factorial.py @@ -0,0 +1,55 @@ +""" +Unit tests for factorial_calculator.py. +""" + +import unittest + +from solutions.factorial import factorial + + +class TestSolutionFactorial(unittest.TestCase): + """ + Unit tests for the factorial function. + """ + + def test_factorial_zero(self): + """ + Test factorial of 0 is 1. + """ + self.assertEqual(factorial(0), 1) + + def test_factorial_one(self): + """ + Test factorial of 1 is 1. + """ + self.assertEqual(factorial(1), 1) + + def test_factorial_positive(self): + """ + Test factorial of a positive integer. + """ + self.assertEqual(factorial(5), 120) + + def test_large_factorial(self): + """ + Test factorial of a larger number. + """ + self.assertEqual(factorial(10), 3628800) + + def test_negative_input(self): + """ + Test that negative inputs raise a ValueError. + """ + with self.assertRaises(ValueError): + factorial(-5) + + def test_non_integer_input(self): + """ + Test that non-integer inputs raise a TypeError. + """ + with self.assertRaises(TypeError): + factorial(5.5) + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_factorial_calculator.py b/solutions/tests/test_factorial_calculator.py new file mode 100644 index 000000000..9cf5e9d65 --- /dev/null +++ b/solutions/tests/test_factorial_calculator.py @@ -0,0 +1,43 @@ +"""unit tests for the factorial function in the factorial calculator module""" + +# Ayham +import unittest + +from ..factorial_calculator import factorial + + +class TestFactorial(unittest.TestCase): + """ + testing the factorial function. (edge cases and invalid inputs) + """ + + def test_factorial_positive(self): + """It should return the factorial of positive integers.""" + self.assertEqual(factorial(5), 120) + + def test_factorial_zero(self): + """it should return the factorial of zero > 1""" + self.assertEqual(factorial(0), 1) + + def test_factorial_negative(self): + """it should raise VAlueError for negative integers input""" + with self.assertRaises(ValueError): + factorial(-5) + + def test_factorial_none(self): + """It should raise a ValueError for None as an input""" + with self.assertRaises(ValueError): + factorial(None) + + def test_factorial_non_integer(self): + """It should raise TypeError when passing non integer values""" + with self.assertRaises(TypeError): + factorial(3.5) + + def test_factorial_large_numbers(self): + """ensuring it would compute the factorial for large inputs""" + self.assertEqual(factorial(14), 87178291200) + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_fuel_gauge.py b/solutions/tests/test_fuel_gauge.py new file mode 100644 index 000000000..7f743115f --- /dev/null +++ b/solutions/tests/test_fuel_gauge.py @@ -0,0 +1,29 @@ +import unittest + +from ..fuel_gauge import fuel_gauge + + +class TestFuelGauge(unittest.TestCase): + """Test cases for the fuel_gauge function.""" + + def test_full(self): + """Test cases where the percentage is 90% or more it should result in F.""" + self.assertEqual(fuel_gauge(90, 100), "F") + + def test_empty(self): + """Test cases where the percentage is 10% or less it should result E.""" + self.assertEqual(fuel_gauge(0, 100), "E") + + def test_percentage(self): + """Test cases where the result is between 90 and 10 it should be M.""" + self.assertEqual(fuel_gauge(23, 100), "M") + + def test_invalid_input(self): + """Test cases for invalid inputs.""" + with self.assertRaises(AssertionError): + fuel_gauge(6, 0) + + def test_negative_input(self): + """Test cases for negative inputs.""" + with self.assertRaises(AssertionError): + fuel_gauge(7, -1) diff --git a/solutions/tests/test_game_greeting.py b/solutions/tests/test_game_greeting.py new file mode 100644 index 000000000..3cb22ebd6 --- /dev/null +++ b/solutions/tests/test_game_greeting.py @@ -0,0 +1,35 @@ +""" +test_game_greeting.py + +this module contains unit tests for the 'game_greeting' function. + +""" + +import unittest + +from ..game_greeting import game_greeting + + +class TestGameGreeting(unittest.TestCase): + """test game greeting function""" + + def test_valid_name(self): + """test the function with a valid name""" + self.assertEqual(game_greeting("Mohamed"), "Welcome, Mohamed to the game") + + def test_empty_string(self): + """test the function with an empty string""" + self.assertEqual(game_greeting(""), "Welcome, to the game") + + def test_special_characters(self): + """test the function with special characters""" + self.assertEqual(game_greeting("mx@z*"), "Welcome, mx@z* to the game") + + def test_integers(self): + """test the function with integers""" + with self.assertRaises(AssertionError): + (game_greeting(573)) + + def test_whitespace_name(self): + """test the function with whitespace""" + self.assertEqual(game_greeting(" "), "Welcome, to the game") diff --git a/solutions/tests/test_harmonic_sequence.py b/solutions/tests/test_harmonic_sequence.py new file mode 100644 index 000000000..d168652c6 --- /dev/null +++ b/solutions/tests/test_harmonic_sequence.py @@ -0,0 +1,81 @@ +""" +Test module for Harmonic sequence function. + +Test categories: + - Standard cases: The input is a list of integers. + - Edge cases: The input is equal to 0 or 1. + - Defensive cases: wrong input types, assertions + +Created on 2024-12-30 +Author: Omnia Mustafa - Abdulgadir +""" + +import unittest + +from ..harmonic_sequence import harmonic_sequence + + +class TestHarmonicSequence(unittest.TestCase): + """Test Harmonic Sequence""" + + # Edge cases: + def test_min_input1(self): + """Test case when all inputs equal to 1""" + self.assertEqual(harmonic_sequence(1, 1, 1), [1.0]) + + def test_n_equal_0(self): + """Test case when n is equal to 0""" + self.assertEqual(harmonic_sequence(1, 1, 0), []) + + def test_a_and_d_equal_0(self): + """Test case when a and d are equal to 0""" + self.assertEqual(harmonic_sequence(0, 0, 5), [0, 0, 0, 0, 0]) + + def test_n_negative(self): + """Test case when n is negative""" + self.assertEqual(harmonic_sequence(1, 1, -1), []) + + def test_a_and_d_equal_1(self): + """Test a and d are equal 1""" + self.assertEqual( + harmonic_sequence(1, 1, 5), [1.0, 0.5, 0.3333333333333333, 0.25, 0.2] + ) + + # Standard cases: + def test_all_inputs_greater_than_1(self): + """Test when all inputs are integer and greater than 1""" + self.assertEqual(harmonic_sequence(2, 2, 3), [0.5, 0.25, 0.16666666666666666]) + + def test_n_is_large(self): + """Test when n is large""" + self.assertEqual( + harmonic_sequence(4, 3, 10), + [ + 0.25, + 0.14285714285714285, + 0.1, + 0.07692307692307693, + 0.0625, + 0.05263157894736842, + 0.045454545454545456, + 0.04, + 0.03571428571428571, + 0.03225806451612903, + ], + ) + + # Defensive cases: + def wrong_input_a_type(self): + """Test when input a is not integer""" + with self.assertRaises(AssertionError): + harmonic_sequence("1", 1, 1) + + def wrong_input_d_type(self): + """Test when input d is not integer""" + with self.assertRaises(AssertionError): + harmonic_sequence(5, "Omnia", 10) + + def wrong_input_n_type(self): + """Test when input n is not integer""" + with self.assertRaises(AssertionError): + harmonic_sequence(1, 1, [1, 2, 3]) diff --git a/solutions/tests/test_is_anagram.py b/solutions/tests/test_is_anagram.py new file mode 100644 index 000000000..968529378 --- /dev/null +++ b/solutions/tests/test_is_anagram.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for is_anagram function. + +Test categories: + - Standard cases: typical string lists + - Edge cases: empty strings, equal lengths + - Defensive tests: invalid inputs + +Created on 2024-01-02 +Author: Reem Osama +""" + +import unittest + +from ..is_anagram import is_anagram + + +class TestIsAnagram(unittest.TestCase): + """Test suite for the is_anagram function""" + + # Standard Cases: + def test_base_one(self): + """A basic test for valid cases""" + self.assertEqual(is_anagram("listen", "silent"), True) + + def test_different_length(self): + """A test for string with unequal lengths""" + self.assertEqual(is_anagram("apple", "apples"), False) + + # Edge Cases: + def test_case_sensitivity(self): + """A test for case sensitivity strings""" + self.assertEqual(is_anagram("Listen", "silent"), False) + + def test_white_space(self): + """A test for whitespace handling""" + self.assertEqual(is_anagram("dormitory", "dirty room"), False) + + def test_space_in_middle(self): + """A test for handling spaces in the middle of two strings""" + self.assertEqual(is_anagram("the eyes", "they see"), True) + + def test_special_characters(self): + """A test with inputs with punctuation or non-alphabetic chars""" + self.assertEqual(is_anagram("a+b=c", "c+b=a"), True) + + def test_empty_strings(self): + """A test with empty strings""" + self.assertEqual(is_anagram("", ""), True) + + def test_single_characters(self): + """A test with single character strings""" + self.assertEqual(is_anagram("a", "b"), False) + + def test_numerical_strings(self): + """A test with numerical strings""" + self.assertEqual(is_anagram("123", "321"), True) + + def test_large_inputs(self): + """A test with large strings""" + large_str1 = "a" * 10000 + "b" * 10000 + large_str2 = "b" * 10000 + "a" * 10000 + self.assertEqual(is_anagram(large_str1, large_str2), True) + + # Defensive tests: + def test_invalid_input(self): + """A test with invalid input""" + with self.assertRaises(AssertionError): + is_anagram(123, "abc") + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_quadratic_sequence.py b/solutions/tests/test_quadratic_sequence.py new file mode 100644 index 000000000..1b91eebc5 --- /dev/null +++ b/solutions/tests/test_quadratic_sequence.py @@ -0,0 +1,77 @@ +""" +Test module for quadratic_sequence function. + +Test categories: + - Standard cases: The input is a list of integers. + - Edge cases: The input is equal to 0 or 1. + - Defensive cases: wrong input types, assertions + +Created on 2024-12-30 +Author: Omnia Mustafa - Abdulgadir +""" + +import unittest + +from ..quadratic_sequence import quadratic_sequence + + +class TestQuadraticSequence(unittest.TestCase): + """ + The class is testing the function quadratic_sequence + in different cases and inputs + """ + + # Edge cases: + def test_all_inputs_are_0(self): + """Test case when all inputs equal to 0""" + self.assertEqual(quadratic_sequence(0, 0, 0, 0), []) + + def test_n_is_0(self): + """Test case when n is equal to 0""" + self.assertEqual(quadratic_sequence(5, 3, 2, 0), []) + + def test_all_inputs_are_1(self): + """Test case when all inputs equal to 1""" + self.assertEqual(quadratic_sequence(1, 1, 1, 1), [3]) + + def test_a_is_0(self): + """Test case when a is equal to 0""" + self.assertEqual(quadratic_sequence(0, 5, 3, 2), [8, 13]) + + def test_b_is_0(self): + """Test case when b is equal to 0""" + self.assertEqual(quadratic_sequence(5, 0, 3, 2), [8, 23]) + + def test_c_is_0(self): + """Test case when c is equal to 0""" + self.assertEqual(quadratic_sequence(5, 3, 0, 2), [8, 26]) + + # Standard cases: + def test_equal_inputs(self): + """Test case when all inputs are equal""" + self.assertEqual(quadratic_sequence(3, 3, 3, 3), [9, 21, 39]) + + def test_different_inputs(self): + """Test case when all inputs are different""" + self.assertEqual(quadratic_sequence(1, 2, 3, 4), [6, 11, 18, 27]) + + # Defensive cases: + def wrong_input_a_type(self): + """Test when input a is not integer""" + with self.assertRaises(AssertionError): + quadratic_sequence("1", 1, 1, 1) + + def wrong_input_b_type(self): + """Test when input b is not integer""" + with self.assertRaises(AssertionError): + quadratic_sequence(5, "Omnia", 10, 6) + + def wrong_input_c_type(self): + """Test when input c is not integer""" + with self.assertRaises(AssertionError): + quadratic_sequence(1, 5, [1, 2, 3], 8) + + def wrong_input_n_type(self): + """Test when input n is not integer""" + with self.assertRaises(AssertionError): + quadratic_sequence(1, 1, 5, [1, 2, 3]) diff --git a/solutions/tests/test_robot.py b/solutions/tests/test_robot.py new file mode 100644 index 000000000..c21b23bb5 --- /dev/null +++ b/solutions/tests/test_robot.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for robot function. + +Test categories: + - Standard cases + - Edge cases + - Defensive tests + +Created on 2024-01-02 +Author: Reem Osama +""" + +import unittest +from ..robot import robot + + +class TestRobot(unittest.TestCase): + """Test the robot function""" + + # Standard Cases + def test_base_1x1(self): + """A basic test with a small grid 1x1""" + self.assertEqual(robot(1, 1), 1) + + def test_base_2x2(self): + """A basic test with a small grid 2x2""" + self.assertEqual(robot(2, 2), 2) + + def test_base_3x3(self): + """A basic test with a small grid 3x3""" + self.assertEqual(robot(3, 3), 6) + + def test_base_3x7(self): + """A basic test with a small grid of 3x7""" + self.assertEqual(robot(3, 7), 28) + + def test_base_4x3(self): + """A basic test with a grid of 4x3""" + self.assertEqual(robot(4, 3), 10) + + # Edge Cases + def test_long_narrow(self): + """A test for a long narrow grid of 1x10""" + self.assertEqual(robot(1, 10), 1) + + def test_tall_narrow(self): + """A test for a tall narrow grid 10x1""" + self.assertEqual(robot(10, 1), 1) + + def test_large_grid(self): + """A test for a moderately large grid 10x10""" + self.assertEqual(robot(10, 10), 48620) + + def test_very_large_grid(self): + """A test for a very large grid 20x15""" + self.assertEqual(robot(20, 15), 818809200) + + # Defensive Cases + def test_invalid_row(self): + """A test for an invalid row input - not an integer""" + with self.assertRaises(AssertionError): + robot("3", 7) + + def test_invalid_col(self): + """A test for an invalid column input - not an integer""" + with self.assertRaises(AssertionError): + robot(3, "7") + + def test_negative_value(self): + """A test with a negative dimension""" + with self.assertRaises(ValueError): + robot(-1, 5) + + def test_zero_value(self): + """A test with zero value""" + with self.assertRaises(ValueError): + robot(3, 0) + + def test_non_integer_input(self): + """A test wit a non integer input for both dimensions""" + with self.assertRaises(AssertionError): + robot(2.5, 3.7) diff --git a/solutions/tests/test_substitution_cipher.py b/solutions/tests/test_substitution_cipher.py new file mode 100644 index 000000000..61f3e29ca --- /dev/null +++ b/solutions/tests/test_substitution_cipher.py @@ -0,0 +1,41 @@ +""" +Unit tests for the cipher function +""" + +import unittest + +from solutions.substitution_cipher import cipher + + +class TestSubstitutionCipher(unittest.TestCase): + """ + testing cipher function + """ + + def test_lowercase(self): + """ + testing lower case encryption + """ + self.assertEqual(cipher("abc"), "def") + + def test_mixed_cases(self): + """ + testing lower and upper case mixed letters + """ + self.assertEqual(cipher("AbCd"), "DeFg") + + def test_edge_cases(self): + """ + testing edge cases such as reaching the end of the alphabets + """ + self.assertEqual(cipher("xyz"), "abc") + + def test_invalid_chars(self): + """ + testing that ivalid chars remain unchanged when mixed with alphabets + """ + self.assertEqual(cipher("Hello@123!"), "Khoor@123!") + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_summation.py b/solutions/tests/test_summation.py new file mode 100644 index 000000000..382471f40 --- /dev/null +++ b/solutions/tests/test_summation.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +unit tests for the summation function +""" + +import unittest + +from solutions.summation import sum + +"""tests for the sum function""" + + +class TestSum(unittest.TestCase): + """It should return the sum of positive numbers""" + + def test_sum_positive_numbers(self): + actual = sum(1, 2, 3, 4, 5) # noqa: F841 + expected = 15 # noqa: F841 + self.assertEqual(actual, expected) # noqa: F821 + + """It should return the sum of a negative numbers""" + + def test_sum_negative_numbers(self): + actual = sum(-1, -2, -3, -5) # noqa: F841 + expected = -11 + self.assertEqual(actual, expected) + + """It should return the sum of a mix numbers""" + + def test_sum_mixed_numbers(self): + actual = sum(10, -20, -40, 50) + expected = 0 + self.assertEqual(actual, expected) + + """It should return the sum of a float numbers""" + + def test_sum_float_numbers(self): + actual = sum(1.5, 2.5, 3.7, 4.5) + expected = 12.2 + self.assertEqual(actual, expected) + + """It should return the sum of zero numbers""" + + def test_sum_zero_numbers(self): + actual = sum(0, 0, 0, 0) + expected = 0 + self.assertEqual(actual, expected) + + """It should return the single argument when only one is provided.""" + + def test_sum_single_argument(self): + actual = sum(10) + expected = 10 + self.assertEqual(actual, expected) + + """It should return 0 when no arguments are provided.""" + + def test_sum_no_arguments(self): + actual = sum() + expected = 0 + self.assertEqual(actual, expected) + + """It should raise a TypeError for string input.""" + + def test_invalid_input_string(self): + with self.assertRaises(TypeError): + sum(1, 2, "string", 4) + + """It should raise a TypeError for None as an input.""" + + def test_invalid_input_none(self): + with self.assertRaises(TypeError): + sum(1, None, 3) + + """It should raise a TypeError for mixed valid and invalid inputs.""" + + def test_invalid_input_mixed(self): + with self.assertRaises(TypeError): + sum(10, 5, "invalid", 2) diff --git a/solutions/tests/test_word_count_from_file.py b/solutions/tests/test_word_count_from_file.py new file mode 100644 index 000000000..7acd3f2fa --- /dev/null +++ b/solutions/tests/test_word_count_from_file.py @@ -0,0 +1,49 @@ +""" +Unit tests for the `word_count_txt_file` function. + +Tests the following scenarios: + - Correct word count for a file with a known number of words. + - Handling of an empty file. + - Word count for a file with multiple lines. + - Assertion errors for non-existent files. + - Assertion errors for non-.txt files. +""" + +import unittest + +from ..word_count_from_file import word_count_txt_file + + +class TestWordCountFromFile(unittest.TestCase): + """Unit tests for the `word_count_txt_file` function.""" + + def test_correct_word_count(self): + """Test if the method correctly counts words in a file with 5 words.""" + actual = word_count_txt_file("solutions/test_5_words.txt") + self.assertEqual(actual, 5, "The word count should be 5.") + + def test_empty_file(self): + """Test if the method returns 0 for an empty file.""" + actual = word_count_txt_file("solutions/test_empty.txt") + self.assertEqual(actual, 0, "word count must be 0 for an empty file.") + + def test_multiple_lines_file(self): + """Test if the method correctly counts words in a multi-line file.""" + actual = word_count_txt_file("solutions/test_multiple.txt") + self.assertEqual( + actual, 10, "The word count should be 10 for a multi-line file." + ) + + def test_file_not_exist(self): + """Test if the method raises an error for a non-existent file.""" + with self.assertRaises( + AssertionError, msg="An error if the file does not exist." + ): + word_count_txt_file("non_existent_file.txt") + + def test_not_a_txt_file(self): + """Test if the method raises an error for a non-.txt file.""" + with self.assertRaises( + AssertionError, msg="Should raise an error for a non-.txt file." + ): + word_count_txt_file("test") diff --git a/solutions/word_count_from_file.py b/solutions/word_count_from_file.py new file mode 100644 index 000000000..7dc4925d7 --- /dev/null +++ b/solutions/word_count_from_file.py @@ -0,0 +1,65 @@ +""" +A Module for counting the number of words in a .txt file. + +Module contents: + - Function: `word_count_txt_file` for counting words in a file. + - Input: `.txt` file to read the content from. + - Output: Total number of words in the file. + +Created on 30 Dec 2024 +@author: Saja Abusafia +""" + +import os + + +def word_count_txt_file(file_name) -> int: + """ + Counts the total number of words in a .txt file. + + Parameters: + file_name (str): The name of the file to read the content from. + + Returns: + int: The total number of words in the file. + + Raises: + AssertionError: If the file does not exist. + AssertionError: If the file is not a .txt file. + + >>> word_count_txt_file("solutions/test_5.txt") + 5 + >>> word_count_txt_file("solutions/test_empty.txt") + 0 + >>> word_count_txt_file("solutions/test_multiple.txt") + 10 + """ + + # Defensive assertions + assert os.path.exists(file_name), f"Error:file '{file_name}' doesn't exist" + assert file_name.endswith(".txt"), ( + f"Error: The file '{file_name}' does not have a .txt extension." + ) + + # Open the file safely + f = open(file_name, "r") + file_size = os.path.getsize(file_name) + + # Return 0 if the file is empty + if file_size == 0: + return 0 + # Read the file's content + file_content = f.read() + + i = 0 + for n in file_content: + if n == "": + i += 1 + break # Don't increment unless it reach the end of a word + elif n == " ": + i += 1 # Increment if the character is a space + elif n == "\n": + i += 1 # Increment if reaches the end of a file + + f.close() + return i