diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6e269b4ad..eba8a7be2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -54,8 +54,10 @@ 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 in the function file +- [ ] The function is not called at the top level of the function file + - _Recursive solutions **can** call the function from **inside** the function body_ ## Strategy @@ -67,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/README.md b/README.md index e69de29bb..5b012cc2e 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,66 @@ +# ZeroThroughTen 🚀 + +Welcome to **ZeroThroughTen**, +a dynamic group of passionate learners united by a shared enthusiasm for coding, collaboration, and creative problem-solving. 🌟 Together, we transform challenges into stepping stones for growth, innovation, and success. 💡 + +At ZeroThroughTen, every contribution matters, every voice is heard, and every member is an integral part of our journey. We believe in learning, growing, and achieving **as a team**—where the strength of one enhances the power of all. 🤝 + +--- + +## ✨ **About Us** + +We are **ZeroThroughTen**, a group of enthusiastic problem-solvers driven by curiosity, creativity, and a commitment to mastering programming skills. We don't just solve problems—we redefine them, turning challenges into opportunities to build impactful and meaningful solutions. 🚀 + +Our group thrives on collaboration, where every idea adds value and every challenge is tackled together. Whether it’s debugging code, brainstorming new ideas, or building innovative solutions, we’re here to **shine brighter, together. 🌈** + +--- + +## 🛠️ **Our Mission** + +- **Learn and Grow**: Sharpen programming and problem-solving skills through hands-on collaboration and experimentation. +- **Collaborate to Innovate**: Build impactful projects while fostering a culture of open communication and shared knowledge. +- **Master the Tools**: Enhance familiarity with GitHub workflows, version control, and best coding practices. +- **Support Each Other**: Cultivate an inclusive and inspiring team environment where every member feels valued. + +--- + +## 🔗 **Communication Channels** + +- **Slack**: Daily updates, task discussions, and real-time collaboration. +- **Weekly Meetings**: Thursdays at 7:00 PM CET (Central European Time) on Zoom. +- **GitHub**: Central hub for code contributions, pull requests, and documentation. + +> Remember, teamwork is the heart of ZeroThroughTen! 💬 + +--- + +## 🧩 **Our Values** + +1. **Collaboration**: Together, we achieve more. +2. **Respect**: Every idea matters, every voice counts. +3. **Curiosity**: Always stay curious, always stay creative. +4. **Accountability**: Take ownership of your tasks while supporting your teammates. +5. **Excellence**: Strive for clean, efficient, and impactful solutions. + +--- + +## 📝 **Our Goals** + +- Build a collaborative project where every member contributes meaningfully. +- Learn to review and improve code collaboratively. +- Explore new tools, frameworks, and technologies together. +- Develop solutions that are innovative, efficient, and impactful. + +--- + +## 🎉 **Acknowledgments** + +A huge **shoutout** to every member of **ZeroThroughTen** for their dedication, hard work, and creativity. 💪 +Each step we take, each line of code we write, brings us closer to our goals—**one breakthrough at a time. 🌟** + +Let’s continue to learn, grow, and shine together. ✨ +**Remember, from zero through ten, the possibilities are infinite. 🔢** + +--- + +Stay curious. Stay creative. Keep coding. 🚀 diff --git a/collaboration/README.md b/collaboration/README.md index 869cc698d..6bd0cf3fa 100644 --- a/collaboration/README.md +++ b/collaboration/README.md @@ -1,11 +1,11 @@ # Collaboration -## Group “ET6-foundations-group-10” +## Group “ZeroThroughTen” -The group is focused on professional communication in developing the _**''“ET6-foundations-group-10”**_ project to implement the _Foundations Track_ syllabus of the [_MIT Emerging Talent_](https://emergingtalent.mit.edu/) program. +The group is focused on professional communication in developing the _**“ZeroThroughTen”**_ project to implement the _Foundations Track_ syllabus of the [_MIT Emerging Talent_](https://emergingtalent.mit.edu/) program. ### Norms of the group -1. We try to communicate on a professional level on issues related to the project “ET6-foundations-group-10” development and implementation of the Foundations Track syllabus of the MIT Emerging Talent program. We do not touch on issues of personal characteristics, religious beliefs, political views, or level of competence. -2. We communicate asynchronously using Slack and GitHub tools, so our communication needs to be documented and other group members can view the subject of the communication, the discussion itself, and the results of the communication. +1. We try to communicate on a professional level on the issues related to the project development and implementation according to the Foundations Track syllabus of the MIT Emerging Talent program. We do not touch on issues of personal characteristics, religious beliefs, political views, or level of competence. +2. We communicate asynchronously using Slack and GitHub tools, so our communication needs to be documented to allow other group members to view the subject of the communication, the discussion itself, and the results of the communication. 3. All group meetings are scheduled in advance so that other group members can organize their time. All group communication is documented. Personal communication can take place online. diff --git a/collaboration/communication.md b/collaboration/communication.md index 484652e0f..7be823233 100644 --- a/collaboration/communication.md +++ b/collaboration/communication.md @@ -9,46 +9,74 @@ # Communication -______________________________________________________________________ +--- ## Communication Schedule -| Day | How | The topic of discussion | | --- | :-: | ----------------------- | -| | | | +| **Day** | **How** | **Topic of Discussion** | +|---------------|--------------------------------|---------------------------| +| Flexible | Slack (Daily) | Quick updates, general communication | +| Twice a week | Google Meet | Progress updates, task discussions, and important decisions | + +--- ## Communication Channels -how often will we get in touch on each channel, and what we will discuss there: +### Frequency and topics for each channel + +- **Slack**: + - **Purpose**: Daily communication, task updates, and sharing quick announcements. + - **Frequency**: Regularly active for asynchronous communication. + +- **Google Meet**: + - **Purpose**: Discuss progress, blockers, and important milestones. + - **Frequency**: Twice per week or as needed based on the project phase. -- **Issues**: -- **Pull Requests**: -- **Slack/Discord**: -- **Video Calls**: +- **GitHub Issues**: + - **Purpose**: Track tasks, report bugs, and plan milestones. + - **Frequency**: Active for task management. -______________________________________________________________________ +- **GitHub Pull Requests**: + - **Purpose**: Code reviews and collaboration. + - **Frequency**: Notified via Slack when a review is needed. + +--- ## Availability -### Availability for calling/messaging +### Team's General Availability + +The team works asynchronously due to different time zones and schedules. To facilitate effective communication: + +- Meeting times are determined by a poll or decided during prior meetings. +- Members are encouraged to stay active on Slack to ensure timely updates. +- Meetings are recorded when possible, and summaries are shared for those unable to attend. -| Day | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday | | ------- | :----: | :-----: | :-------: | :------: | :----: | :------: | :----: | -| _name_ | | | | | | | | +### Response Time -### How many hours everyone has per day +- **Messages**: Responses on Slack are expected within **12 hours**. +- **Code Reviews**: Pull requests are reviewed within **24 hours** whenever possible. -- name: _5h_; -- name: _6h_; -- name: _5h_; -- name: _4h_; -- name: _3h_; +--- ## Asking for Help -There's a fine line between confidently learning from your mistakes, and -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: +Here’s a general guide for when to ask for help if you’re stuck: + +1. **0 -> 30 minutes**: Try solving the problem independently using available resources. +2. **30 -> 60 minutes**: Reach out to the group via Slack for input or support. +3. **60+ minutes**: If still unresolved, tag a coach or mentor on Slack or GitHub. + +### Key Note + +Don’t hesitate to ask for help when needed. Collaboration is about supporting each other to achieve the project goals. + +--- + +## Meeting Agendas + +Meeting agendas will be created before each scheduled meeting: -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 +- Agendas are drafted using GitHub Issues, where members can add discussion points. +- Meeting minutes and action items are recorded and shared in the issue comments or via Slack. +- All agenda points are addressed before closing the meeting issue. diff --git a/collaboration/constraints.md b/collaboration/constraints.md index 24079505c..4a9c9538f 100644 --- a/collaboration/constraints.md +++ b/collaboration/constraints.md @@ -4,31 +4,39 @@ Some boundaries around our project. -## External - - - -## Internal: Involuntary - - - -## Internal: Voluntary - - +--- + +## **External** + + + +- **Deadline:** The project must be completed by **10 Jan 2025**. +- **Programming Language:** The programming language for this project is **Python** and cannot be changed. +- **Coding Standards:** All coding, documentation, and reviews must adhere to established standards and examples. +- **Restricted Pushes:** Direct pushes to the main branch are prohibited. +- **Branch Merging Rules:** Branches can only be merged after a pull request is reviewed and approved. +- **Team Size:** The team consists of 10 members. However, active participation will be observed throughout the process, and adjustments will be made as needed to ensure tasks are distributed effectively and project goals are met. This flexible approach allows the team to adapt based on member availability and engagement. + +--- + +## **Internal: Involuntary** + + + +- **Different Schedules:** Team members have varying commitments, making synchronous meetings difficult. +- **Learning Together:** Some members are still learning relevant skills, requiring collaboration and support. +- **Limited Availability:** Balancing this project with other responsibilities might reduce availability at times. +- **Knowledge Gaps:** Some members lack experience in specific technologies or concepts, which may slow progress. +- **Dependency on Others:** Certain tasks depend on the completion of others, potentially causing bottlenecks. +- **Limited Review Capacity:** Fewer reviewers might delay pull request approvals and overall progress. + +--- + +## **Internal: Voluntary** + + + +- **Scope Management:** The project scope is deliberately kept small to ensure timely completion and avoid unnecessary complexity. +- **Adherence to Rules:** The team agrees to follow coding style guides, documentation standards, and review conventions. +- **Collaboration Commitment:** All members are dedicated to equal participation and supporting one another. +- **Documentation Priority:** All tasks, decisions, and code will be properly documented to facilitate collaboration. diff --git a/collaboration/learning_goals.md b/collaboration/learning_goals.md index 11c583d2b..7df5105bf 100644 --- a/collaboration/learning_goals.md +++ b/collaboration/learning_goals.md @@ -1,5 +1,37 @@ # Learning Goals +## Group Objectives + +The primary goal of our group is to enhance our understanding and practical skills in software development by working collaboratively on exercises. Each member brings unique objectives, and we aim to create a supportive environment where these individual goals can be achieved. + ## Collective +1. **Enhance Problem-Solving Skills**: Apply data structures (DSs) and algorithms to solve complex problems effectively. +2. **Learn and Practice New Techniques**: Explore new programming language features and techniques to improve code quality. +3. **Focus on Readability and Documentation**: Develop solutions that are well-documented, clear, and maintainable. +4. **Improve Development Speed**: Practice strategies to write and debug code faster, aiming for efficiency. +5. **Collaborative Code Reviews**: Engage in multiple review cycles to learn from feedback and refine solutions collaboratively. + +--- + ## Individual + +### Emre Biyik + +- Review data structures and algorithms studied previously. +- Focus on writing more complete and readable solutions. +- Gain deeper experience with version control tools like Git and GitHub, especially in team projects. + +### Oleksandr Maksymikhin + +- Study the Python programming language. +- Improve skills for participation in group projects using the version control system GitHub. +- Investigate the functionality of GitHub (Issues, Project Board, Actions, CI/CD). + +--- + +## Group Approach + +- Regularly review and reflect on progress towards these goals during meetings. +- Support each other in achieving individual and shared objectives through collaborative exercises. +- Use tools like GitHub Issues and Project Boards to ensure visibility and accountability. diff --git a/collaboration/retrospective.md b/collaboration/retrospective.md index 74e18813b..62919c9e1 100644 --- a/collaboration/retrospective.md +++ b/collaboration/retrospective.md @@ -1,23 +1,77 @@ - - # Retrospective +> “Regardless of what we discover, we understand and truly believe that everyone +> did the best job they could, given what they knew at the time, their skills +> and abilities, the resources available, and the situation at hand.” +> +> - [Norm Kerth](http://www.amazon.com/Project-Retrospectives-Handbook-Reviews-Dorset-ebook/dp/B00DY3KQJU/ref=tmm_kin_swatch_0?_encoding=UTF8&sr=&qid=) + +--- + ## Stop Doing +- **Challenges Faced:** + - Initial difficulties in understanding the complete GitHub workflow. + - Errors during the push process from local machines to GitHub, such as merge conflicts or incomplete changes. + - Limited initial familiarity with GitHub’s collaborative features, leading to delays in resolving issues. + +*Takeaway:* Invest more time in understanding GitHub tools and workflows at the start of the project to minimize errors and delays. + +--- + ## Continue Doing +- **What Worked Well:** + - Collaborative problem-solving improved over time, especially during CI/CD checks and error resolution. + - Regular document reviews and feedback fostered team learning and improved overall quality. + - Clear documentation and communication in Slack channels helped the team address challenges effectively. + +*Takeaway:* Continue building on strong collaboration and communication practices. + +--- + ## Start Doing +- **New Ideas for Future Projects:** + - Document the step-by-step GitHub workflow for all team members to refer to during the project. + - Establish a shared resource for documenting common errors and solutions (e.g., troubleshooting merge conflicts or CI errors). + - Schedule regular review sessions to identify errors earlier in the project lifecycle. + +*Takeaway:* Implement these strategies to ensure a smoother and more efficient workflow in future projects. + +--- + ## Lessons Learned -______________________________________________________________________ +- Working on the GitHub Team Project provided invaluable insights into: + - Pushing updates from local machines to GitHub and managing version control. + - Troubleshooting challenges like merge conflicts, rough draft errors, and CI/CD issues. + - Reviewing, commenting, and improving code collaboratively. + - Observing the step-by-step lifecycle of a project, from initiation to finalization. + - The importance of effective communication and documentation in ensuring project success. + +These lessons enhanced both technical and collaboration skills, laying the foundation for better teamwork in future projects. + +--- ## Strategy vs. Board -### What parts of your plan went as expected? +- **What parts of your plan went as expected?** + - GitHub collaboration improved over time with regular communication and troubleshooting. + - Document reviews and feedback loops were successfully implemented. + +- **What parts of your plan did not work out?** + - Early misunderstandings of GitHub workflows caused delays. + +- **Did you need to add things that weren't in your strategy?** + - Yes, additional time and resources were allocated for troubleshooting and learning GitHub features. -### What parts of your plan did not work out? +- **Or remove extra steps?** + - Simplifying the review and feedback process helped focus on key areas. -### Did you need to add things that weren't in your strategy? +--- -### Or remove extra steps? +This retrospective reflects the challenges, achievements, and lessons learned during the GitHub Team Project. +The project provided an invaluable opportunity to see a software development process in its entirety, from local pushes to collaborative reviews and debugging. +Moving forward, these experiences will guide us in delivering even more efficient and successful projects. +Thank you all for your dedication and effort! diff --git a/solutions/README.md b/solutions/README.md index 9852346d2..420ed1e86 100644 --- a/solutions/README.md +++ b/solutions/README.md @@ -1 +1,21 @@ # Solutions + +## Palindrome Checker + +This module checks if a given string is a palindrome. A palindrome reads the same backward as forward, ignoring case and spaces. + +### How to Use + +1. Run the `is_palindrome.py` file to use the function. +2. Use `test_is_palindrome.py` to test the functionality. + +#### Examples + +```python +>>> from is_palindrome import is_palindrome +>>> is_palindrome("level") +True +>>> is_palindrome("hello") +False +>>> is_palindrome("A man a plan a canal Panama") +True diff --git a/solutions/ascending_order_18/__init__.py b/solutions/ascending_order_18/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/solutions/ascending_order_18/ascending_order.py b/solutions/ascending_order_18/ascending_order.py new file mode 100644 index 000000000..6a2be56df --- /dev/null +++ b/solutions/ascending_order_18/ascending_order.py @@ -0,0 +1,32 @@ +"""this is a solution for the ascending order problem.""" + + +def sort_numbers(num_list): + """ + Sorts a list of numbers in ascending order. + + Args: + num_list (list): A list of numbers (integers or floats). + + Returns: + list: A new list containing the numbers sorted in ascending order. + """ + return sorted(num_list) + + +def get_input(): + """ + Takes a comma-separated string input from the user + converts it to a list of numbers, and sorts them. + + Returns: + list: A sorted list of numbers. + """ + user_input = input("Enter a list of numbers separated by commas: ") + num_list = [int(num.strip()) for num in user_input.split(",")] + return sort_numbers(num_list) + + +if __name__ == "__main__": + sorted_list = get_input() + print(f"Sorted list: {sorted_list}") diff --git a/solutions/bubble_sort.py b/solutions/bubble_sort.py new file mode 100644 index 000000000..acee5cb30 --- /dev/null +++ b/solutions/bubble_sort.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for sorting collection using bubble sort algorithm. +Processed types: int, float, str, bool + +Module contents: + - bubble_sort: sort the collection in ascending order. + +Created on 2024-01-02 +Author: Oleksandr Maksymikhin +""" + +from typing import TypeVar + +T = TypeVar("T", int, float, str, bool) + + +# def bubble_sort(input_collection: list[int]) -> list[int]: +def bubble_sort(input_collection: list[T]) -> list[T]: + """Sort collection using bubble sort algorithm. + + Sort elements in collection by swapping and moving larger elements to the end of collection. + + Parameters: + input_collection: list[T], collection of unsorted data with data types (T): int, float, str, bool. + + Returns -> list[T], collection of sorted elements with data types (T): int, float, str, bool. + + Raises: + AssertionError: if input is not a collection. + AssertionError: if collection contains elements of different data types (non-homogeneous). + + Examples: + >>> bubble_sort([1]) + [1] + >>> bubble_sort([1, 3, 2]) + [1, 2, 3] + >>> bubble_sort([3, 2, 100500, 1]) + [1, 2, 3, 100500] + """ + + # defensive assertion to check that input is a collection list + assert isinstance(input_collection, list), "Input is not a collection" + + # defensive assertion to check that collection is homogeneous + assert all( + isinstance(item, type(input_collection[0])) for item in input_collection + ), "Input collection is not homogeneous" + + # copy collection to avoid side effect + collection = input_collection.copy() + # define collection length + collection_length = len(collection) + # first loop to traverse the collection + for current_item_index in range(collection_length): + # flag to break if the last run didn't swap any item + already_sorted = True + # second loop to compare/swap item with adjacent one + for swap_index in range(collection_length - current_item_index - 1): + # swap items if the next adjacent item is smaller + if collection[swap_index] > collection[swap_index + 1]: + (collection[swap_index], collection[swap_index + 1]) = ( + collection[swap_index + 1], + collection[swap_index], + ) + already_sorted = False + # break loop if the last run didn't swap any item + if already_sorted: + break + # return sorted collection + return collection diff --git a/solutions/is_palindrome.py b/solutions/is_palindrome.py new file mode 100644 index 000000000..b55b46ea3 --- /dev/null +++ b/solutions/is_palindrome.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for checking if a string is a palindrome. + +Module contents: + - is_palindrome: Checks if a given string is a palindrome. + +Created on 2024-12-30 +Author: Emre Biyik +""" + + +def is_palindrome(text: str) -> bool: + """Checks if a string is a palindrome. + + A palindrome is a word, phrase, or sequence that reads the same backward as forward, ignoring case and spaces. + + Parameters: + text: str, the input string to check. + + Returns -> bool: True if the input is a palindrome, False otherwise. + + Raises: + AssertionError: if input is not a string. + + Examples: + >>> is_palindrome("level") + True + >>> is_palindrome("hello") + False + >>> is_palindrome("A man a plan a canal Panama") + True + >>> is_palindrome("$+$") + True + >>> is_palindrome("$+#") + False + """ + # Ensure the input is of type string to avoid unexpected errors. + assert isinstance(text, str), "Input must be a string" + + # Normalize the text: remove spaces and convert to lowercase + normalized = "".join([char.lower() for char in text if not char.isspace()]) + + # Check if the string is the same when reversed + return normalized == normalized[::-1] + + +if __name__ == "__main__": + # Example cases to test functionality + print(is_palindrome("$+$")) # Should return True + print(is_palindrome("$+#")) # Should return False + print(is_palindrome("A man a plan a canal Panama")) # Should return True diff --git a/solutions/merge_sort.py b/solutions/merge_sort.py new file mode 100644 index 000000000..14ccbac7e --- /dev/null +++ b/solutions/merge_sort.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for sorting non empty collection of build-in elementary Python data types +(int, float, str, bool) using merge sorting algorithm. + +Module contents: + - merge_sort: function that splits the collection and launches the merge, + - merge: function that merges two sorted collections into one sorted collection. + +Created on 2024-01-05 +Author: Oleksandr Maksymikhin +""" + +from typing import TypeVar + +# from solutions.merge import merge + +T = TypeVar("T", int, float, str, bool) + + +def merge_sort(input_collection: list[T]) -> list[T]: + """Sort collection using merge sorting algorithm. + + Sort elements in collection by splitting the collection in two parts and launch merge. + + Parameters: + - input_collection: list[T], collection of unsorted elements of data types (int, float, string, bool). + + Returns -> list[T], collection of sorted elements of data types (int, float, string, bool). + + Raises: + - AssertionError: if input is not a collection. + - AssertionError: if input collection is empty. + - AssertionError: if input is not a data type processed by module (int, float, string, bool). + - AssertionError: if collection contains elements of different data types (non-homogeneous). + + Examples: + >>> merge_sort([1]) + [1] + >>> merge_sort([1, 3, 2]) + [1, 2, 3] + >>> merge_sort([3, 2, 100500, 1]) + [1, 2, 3, 100500] + >>> merge_sort(['banana', 'apple', 'cherry']) + ['apple', 'banana', 'cherry'] + """ + + # defensive assertion to check that input is a collection list + assert isinstance(input_collection, list), "Input is not a collection" + + # defensive assertion to check that input collection is not empty + assert len(input_collection) > 0, "Collection is empty" + + # defensive assertion to check that input data types are any of (int, float, string, bool) + assert ( + isinstance(input_collection[0], int) + or isinstance(input_collection[0], float) + or isinstance(input_collection[0], str) + or isinstance(input_collection[0], bool) + ), ( + "Input data types is not processed. Processed data types (int, float, string, bool)" + ) + + # defensive assertion to check that collection is homogeneous + assert all( + isinstance(item, type(input_collection[0])) for item in input_collection + ), "Collection is not homogeneous" + + # copy collection to avoid side effect + collection = input_collection.copy() + if len(collection) < 2: + return input_collection + + # divide collection to two parts + split_index = len(collection) // 2 + + left = merge_sort(collection[:split_index]) + right = merge_sort(collection[split_index:]) + + # return merged collection, sorting left and right parts + return merge(left, right) + + +def merge(left: list[T], right: list[T]) -> list[T]: + """Merge two sorted collections into one sorted collection. + + Combine two sorted homogeneous collections into one sorted collection + + Parameters: + - left: list[T], first sorted collection of data types (int, float, string, bool). + - right: list[T], second sorted collection of data types (int, float, string, bool). + + Returns -> list[T], sorted collection of data types (int, float, string, bool). + + Raises: + - AssertionError: left is not a collection. + - AssertionError: right is not a collection. + - AssertionError: left collection contains elements of different data types (non-homogeneous). + - AssertionError: right collection contains elements of different data types (non-homogeneous). + - AssertionError: left and right contain elements of different data types(non-homogeneous). + + Examples: + >>> merge([2], [1]) + [1, 2] + >>> merge([1, 5], [2, 6]) + [1, 2, 5, 6] + >>> merge([2, 5, 100500], [1, 9]) + [1, 2, 5, 9, 100500] + """ + # defensive assertion to check that input is a collections are lists + assert isinstance(left, list), "Input left is not a collection" + assert isinstance(right, list), "Input right is not a collection" + + # defensive assertion to check that input collections are homogeneous + assert all(isinstance(item, type(left[0])) for item in left), ( + "Input left is not homogeneous" + ) + assert all(isinstance(item, type(right[0])) for item in right), ( + "Input right is not homogeneous" + ) + assert isinstance(left[0], type(right[0])), ( + "Input left and right are not homogeneous" + ) + + # check if left or right collection is empty + if len(left) == 0: + return right + if len(right) == 0: + return left + + # create empty collection for merge + merged_collection = [] + # create indexed to traverse collections + index_left = index_right = 0 + + # merge collections until length of merged collection is smaller + # then length of left and right collections + while len(merged_collection) < len(left) + len(right): + if left[index_left] <= right[index_right]: + merged_collection.append(left[index_left]) + index_left += 1 + else: + merged_collection.append(right[index_right]) + index_right += 1 + + # if left or right collection becomes empty + # add elements of the other collection and break loop + if index_left == len(left): + merged_collection += right[index_right:] + break + if index_right == len(right): + merged_collection += left[index_left:] + break + + return merged_collection diff --git a/solutions/register_user.py b/solutions/register_user.py new file mode 100644 index 000000000..a26d45b20 --- /dev/null +++ b/solutions/register_user.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for validating user registration inputs. + +Module contents: + - register_user: Validates user details and returns a registration dictionary. + +Created on: 2025-01-03 +Author: Emre Biyik +""" + + +def register_user(name: str, age: int, email: str) -> dict: + """ + Validates the parameters (name, age, email) provided for user registration. + + Parameters: + name (str): User's name. Must contain only alphabetic characters. + age (int): User's age. Must be between 18 and 99 (inclusive). + email (str): User's email. Must be in a valid format. + + Returns: + dict: A dictionary containing the user's details. + + Raises: + ValueError: If name contains non-alphabetic characters. + ValueError: If age is under 18 or over 99 years old. + ValueError: If email does not include symbols "@" and "." (in the part after the "@" separator). + + Examples: + >>> register_user("Alice", 25, "alice@example.com") + {'name': 'Alice', 'age': 25, 'email': 'alice@example.com'} + + >>> register_user("Bob", 18, "bob@example.net") + {'name': 'Bob', 'age': 18, 'email': 'bob@example.net'} + + >>> register_user("Dave", 30, "dave@example.org") + {'name': 'Dave', 'age': 30, 'email': 'dave@example.org'} + """ + # Check if name contains only alphabetic characters + if not name.isalpha(): + raise ValueError("Invalid name: Must contain only alphabetic characters.") + + # Check if age is within the valid range (18-99) + if not isinstance(age, int) or not (18 <= age <= 99): + raise ValueError("Invalid age: Must be an integer between 18 and 99.") + + # Check if email contains "@" and "." + if "@" not in email or "." not in email.split("@")[-1]: + raise ValueError("Invalid email: Must be a valid email format.") + + return {"name": name, "age": age, "email": email} diff --git a/solutions/temperature_converter.py b/solutions/temperature_converter.py new file mode 100644 index 000000000..fef471668 --- /dev/null +++ b/solutions/temperature_converter.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for converting temperatures between Celsius and Fahrenheit. + +Module contents: + - celsius_to_fahrenheit: Converts a temperature from Celsius to Fahrenheit. + - fahrenheit_to_celsius: Converts a temperature from Fahrenheit to Celsius. + +Created on 01.05.2025 +@author: Ahmad Hamed Dehzad +""" + +# def celsius_to_fahrenheit(celsius): +# return (celsius * 9/5) + 32 +# +# def fahrenheit_to_celsius(fahrenheit): +# return (fahrenheit - 32) * 5/9 + +def celsius_to_fahrenheit(celsius: float) -> float: + """Converts a temperature from Celsius to Fahrenheit. + + Parameters: + celsius: float, the temperature in Celsius. + + Returns: + float: The temperature in Fahrenheit. + + Raises: + ValueError: If the input is not a number. + + >>> celsius_to_fahrenheit(0) + 32.0 + + >>> celsius_to_fahrenheit(100) + 212.0 + + >>> celsius_to_fahrenheit(-40) + -40.0 + """ + if not isinstance(celsius, (int, float)): + raise ValueError("Invalid input: Temperature must be a number.") + + return (celsius * 9 / 5) + 32 + + +def fahrenheit_to_celsius(fahrenheit: float) -> float: + """Converts a temperature from Fahrenheit to Celsius. + + Parameters: + fahrenheit: float, the temperature in Fahrenheit. + + Returns: + float: The temperature in Celsius. + + Raises: + ValueError: If the input is not a number. + + >>> fahrenheit_to_celsius(32) + 0.0 + + >>> fahrenheit_to_celsius(212) + 100.0 + + >>> fahrenheit_to_celsius(-40) + -40.0 + """ + if not isinstance(fahrenheit, (int, float)): + raise ValueError("Invalid input: Temperature must be a number.") + + return (fahrenheit - 32) * 5 / 9 \ No newline at end of file diff --git a/solutions/tests/test_ascending_order.py b/solutions/tests/test_ascending_order.py new file mode 100644 index 000000000..cbccaf64f --- /dev/null +++ b/solutions/tests/test_ascending_order.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +"""This module contains the unit tests for the sort_numbers function.""" + +import unittest +from solutions.ascending_order_18.ascending_order import sort_numbers + + +class TestSortNumbers(unittest.TestCase): + """tests for the sort_numbers function""" + + def test_empty_list(self): + """Test if the function handles an empty list and returns an empty list.""" + self.assertEqual(sort_numbers([]), []) + + def test_sorted_list(self): + """Test if the function returns the correct sorted list.""" + self.assertEqual(sort_numbers([5, 2, 9, 1, 5, 6]), [1, 2, 5, 5, 6, 9]) + + def test_negative_and_positive_numbers(self): + """Test if the function handles negative and positive numbers correctly.""" + self.assertEqual(sort_numbers([5, -3, 9, -1, 0, 6]), [-3, -1, 0, 5, 6, 9]) + + def test_large_numbers(self): + """Test if the function handles large numbers correctly.""" + self.assertEqual( + sort_numbers([200000, 500000, 1000000]), [200000, 500000, 1000000] + ) diff --git a/solutions/tests/test_bubble_sort.py b/solutions/tests/test_bubble_sort.py new file mode 100644 index 000000000..ec5e331cb --- /dev/null +++ b/solutions/tests/test_bubble_sort.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for bubble_sort() function. + +Test categories: + - Standard cases: lists of type int with different lengths + - Edge cases: empty lists, single element + - Different data types: char, string, bool + - Defensive tests: + - side effects protection + - input is not a collection + - different data types in the input collection (non-homogeneous) + +Created on 2024-01-03 +Author: Oleksandr Maksymikhin +""" + +import unittest + +from ..bubble_sort import bubble_sort + + +class TestBubbleSort(unittest.TestCase): + """Test the bubble_sort function.""" + + def test_1_empty_list(self): + """It should return [] for input []""" + actual = bubble_sort([]) + expected = [] + self.assertEqual(actual, expected) + + def test_2_one_int_element_list(self): + """It should return [1] for input [1]""" + actual = bubble_sort([1]) + expected = [1] + self.assertEqual(actual, expected) + + def test_3_two_int_elements_list(self): + """It should return [1, 2] for input [2, 1]""" + actual = bubble_sort([2, 1]) + expected = [1, 2] + self.assertEqual(actual, expected) + + def test_4_three_int_elements_list(self): + """It should return [1, 2, 3] for input [3, 2, 1]""" + actual = bubble_sort([3, 2, 1]) + expected = [1, 2, 3] + self.assertEqual(actual, expected) + + def test_5_four_int_elements_list_big_number(self): + """It should return [1, 2, 3, 100500] for input [2, 3, 100500, 1]""" + actual = bubble_sort([2, 3, 100500, 1]) + expected = [1, 2, 3, 100500] + self.assertEqual(actual, expected) + + def test_6_char_elements_list(self): + """It should return ["a", "b", "c"] for input ["b", "a", "c"]""" + actual = bubble_sort(["b", "a", "c"]) + expected = ["a", "b", "c"] + self.assertEqual(actual, expected) + + def test_7_string_elements_list(self): + """It should return ["apple", "banana", "cabbage"] for input ["cabbage", "banana", "apple"]""" + actual = bubble_sort(["cabbage", "banana", "apple"]) + expected = ["apple", "banana", "cabbage"] + self.assertEqual(actual, expected) + + def test_8_bool_elements_list(self): + """It should return [False, False, True, True] for input [True, False, True, False]""" + actual = bubble_sort([True, False, True, False]) + expected = [False, False, True, True] + self.assertEqual(actual, expected) + + def test_9_side_effect_protection(self): + """It should return [3, 2, 1] of initial input""" + input_list = [3, 2, 1] + bubble_sort(input_list) + self.assertEqual(input_list, [3, 2, 1]) + + def test_10_non_collection_input(self): + """It should raise an assertion error if the input is not a collection""" + with self.assertRaises(AssertionError): + bubble_sort("apple") + + def test_11_non_homogeneous_collection_input(self): + """It should raise an assertion error if the collection is non-homogeneous""" + with self.assertRaises(AssertionError): + bubble_sort([3, "two", 1]) diff --git a/solutions/tests/test_is_palindrome.py b/solutions/tests/test_is_palindrome.py new file mode 100644 index 000000000..7a4a562ae --- /dev/null +++ b/solutions/tests/test_is_palindrome.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Unit tests for the is_palindrome function. + +This module contains test cases for the is_palindrome function, +which checks if a string is a palindrome. The tests cover: + +- Empty strings +- Single-character strings +- Palindromes with lowercase, mixed-case, and spaces +- Palindromes containing numbers +- Palindromes containing special characters +- Non-palindromes + +Created on 2024-12-30 +Author: Emre Biyik +""" + +import unittest +from solutions.is_palindrome import is_palindrome + + +class TestIsPalindrome(unittest.TestCase): + """Test the is_palindrome function.""" + + def test_empty_string(self): + """It should return True for an empty string.""" + self.assertTrue(is_palindrome("")) + + def test_single_character(self): + """It should return True for single character strings.""" + self.assertTrue(is_palindrome("a")) + + def test_level_palindrome(self): + """It should return True for the palindrome 'level'.""" + self.assertTrue(is_palindrome("level")) + + def test_radar_palindrome(self): + """It should return True for the palindrome 'radar'.""" + self.assertTrue(is_palindrome("radar")) + + def test_hello_is_not_palindrome(self): + """It should return False for the non-palindrome 'hello'.""" + self.assertFalse(is_palindrome("hello")) + + def test_world_is_not_palindrome(self): + """It should return False for the non-palindrome 'world'.""" + self.assertFalse(is_palindrome("world")) + + def test_mixed_case_palindromes_return_true(self): + """It should return True for palindromes with mixed lower-case and upper-case characters.""" + self.assertTrue(is_palindrome("RaceCar")) + + def test_palindromes_with_spaces_return_true(self): + """It should return True for palindromes with spaces and mixed cases.""" + self.assertTrue(is_palindrome("A man a plan a canal Panama")) + + def test_palindrome_with_numbers_returns_true(self): + """It should return True for palindromes containing numbers.""" + self.assertTrue(is_palindrome("12321")) + + def test_non_palindrome_numbers_return_false(self): + """It should return False for non-palindrome numbers.""" + self.assertFalse(is_palindrome("12345")) + + def test_palindrome_with_special_characters_returns_true(self): + """It should return True for palindromes with special characters.""" + self.assertTrue(is_palindrome("!@#$%^&*()_+_)(*&^%$#@!")) + + def test_non_palindrome_with_special_characters_returns_false(self): + """It should return False for non-palindromes with special characters.""" + self.assertFalse(is_palindrome("hello!")) + + def test_special_characters_palindrome_positive(self): + """It should return True for special character palindrome.""" + self.assertTrue(is_palindrome("$+$")) # Palindrome + + def test_special_characters_palindrome_negative(self): + """It should return False for non-palindrome special characters.""" + self.assertFalse(is_palindrome("$+#")) # Non-palindrome + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_merge_sort.py b/solutions/tests/test_merge_sort.py new file mode 100644 index 000000000..062dd5d10 --- /dev/null +++ b/solutions/tests/test_merge_sort.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for merge_sort() function. +Test sorting of homogeneous collections types (int, float, string, bool) using merge algorithm. + +Test categories: + - Standard cases: lists of type int with different lengths + - Edge cases: single element type int + - Different data type: str, bool + - Defensive tests: + - side effects protection + - input is not a collection + - input of empty collection + - different data types in the input collection (non-homogeneous) + - input data types that are not processed by module + +Created on 2024-01-05 +Author: Oleksandr Maksymikhin +""" + +import unittest + +from ..merge_sort import merge_sort + + +class TestMergeSort(unittest.TestCase): + """Test the merge_sort function.""" + + def test_single_int_element_list(self): + """It should return [1] for input [1]""" + actual = merge_sort([1]) + expected = [1] + self.assertEqual(actual, expected) + + def test_two_int_elements_list(self): + """It should return [1, 2] for input [2, 1]""" + actual = merge_sort([2, 1]) + expected = [1, 2] + self.assertEqual(actual, expected) + + def test_three_int_elements_list(self): + """It should return [1, 2, 3] for input [3, 2, 1]""" + actual = merge_sort([3, 2, 1]) + expected = [1, 2, 3] + self.assertEqual(actual, expected) + + def test_four_int_elements_list_big_number(self): + """It should return [1, 2, 3, 100500] for input [2, 3, 100500, 1]""" + actual = merge_sort([2, 3, 100500, 1]) + expected = [1, 2, 3, 100500] + self.assertEqual(actual, expected) + + def test_char_elements_list(self): + """It should return ["a", "b", "c", "d"] for input ["b", "d", "a", "c"]""" + actual = merge_sort(["b", "d", "a", "c"]) + expected = ["a", "b", "c", "d"] + self.assertEqual(actual, expected) + + def test_str_elements_list(self): + """It should return ["apple", "banana", "cabbage", "daikon"] for input ["daikon", "cabbage", "banana", "apple"]""" + actual = merge_sort(["daikon", "cabbage", "banana", "apple"]) + expected = ["apple", "banana", "cabbage", "daikon"] + self.assertEqual(actual, expected) + + def test_bool_elements_list(self): + """It should return [False, False, True, True] for input [True, False, False, True]""" + actual = merge_sort([True, False, False, True]) + expected = [False, False, True, True] + self.assertEqual(actual, expected) + + def test_side_effect_protection(self): + """It should return [3, 2, 1] of initial input""" + input_list = [3, 2, 1, 100500] + copy_for_sorting = input_list.copy() + merge_sort(copy_for_sorting) + self.assertEqual(input_list, [3, 2, 1, 100500]) + + def test_non_collection_input(self): + """It should raise an assertion error if the input is not a collection""" + with self.assertRaises(AssertionError): + merge_sort("banana") + + def test_non_homogeneous_collection_input(self): + """It should raise an assertion error if the collection is non-homogeneous""" + with self.assertRaises(AssertionError): + merge_sort([3, 2, "one"]) + + def test_input_of_empty_collection(self): + """It should raise an assertion error if the input data type is out of processed type""" + with self.assertRaises(AssertionError): + merge_sort([]) + + def test_input_of_non_processed_data_type(self): + """It should raise an assertion error if the input data type is out of processed type""" + with self.assertRaises(AssertionError): + merge_sort([{1, "one"}, {2, "two"}]) diff --git a/solutions/tests/test_register_user.py b/solutions/tests/test_register_user.py new file mode 100644 index 000000000..32ca95373 --- /dev/null +++ b/solutions/tests/test_register_user.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for register_user function. + +Test categories: + - Standard cases: valid inputs + - Edge cases: minimum and maximum valid ages + - Defensive tests: invalid inputs for name, age, and email + +Created on: 2025-01-03 +Author: Emre Biyik +""" + +import unittest +from solutions.register_user import register_user + + +class TestRegisterUser(unittest.TestCase): + """Test suite for the register_user function.""" + + # Standard cases + def test_valid_inputs(self): + """It should register a user with valid details.""" + result = register_user("Alice", 25, "alice@example.com") + expected = {"name": "Alice", "age": 25, "email": "alice@example.com"} + self.assertEqual(result, expected) + + # Edge cases + def test_minimum_age(self): + """It should allow the minimum valid age.""" + self.assertEqual( + register_user("Bob", 18, "bob@example.net"), + {"name": "Bob", "age": 18, "email": "bob@example.net"}, + ) + + def test_maximum_age(self): + """It should allow the maximum valid age.""" + self.assertEqual( + register_user("Charlie", 99, "charlie@example.org"), + {"name": "Charlie", "age": 99, "email": "charlie@example.org"}, + ) + + # Defensive tests + def test_invalid_name(self): + """It should raise ValueError for invalid names.""" + with self.assertRaises(ValueError): + register_user("Alice123", 25, "alice@example.com") + + def test_invalid_age_below_minimum(self): + """It should raise ValueError for ages below 18.""" + with self.assertRaises(ValueError): + register_user("Alice", 17, "alice@example.com") + + def test_invalid_age_above_maximum(self): + """It should raise ValueError for ages above 99.""" + with self.assertRaises(ValueError): + register_user("Alice", 100, "alice@example.com") + + def test_invalid_email_format(self): + """It should raise ValueError for invalid email formats.""" + with self.assertRaises(ValueError): + register_user("Alice", 25, "aliceexample.com") + + def test_multiple_invalid_inputs(self): + """It should raise ValueError for multiple invalid inputs.""" + with self.assertRaises(ValueError): + register_user("Alice123", 17, "aliceexample.com") + + +if __name__ == "__main__": + unittest.main() diff --git a/solutions/tests/test_temperature_converter.py b/solutions/tests/test_temperature_converter.py new file mode 100644 index 000000000..a80160d30 --- /dev/null +++ b/solutions/tests/test_temperature_converter.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This is a test module for converting temperatures between Celsius and Fahrenheit. + +Test categories: + - Standard cases: typical temperature values + - Edge cases: extreme temperatures and boundary values + - Defensive tests: non-numeric inputs + +Created on 01.05.2025 + +@author: Ahmad Hamed Dehzad +""" + +import unittest + +from ..temperature_converter import celsius_to_fahrenheit, fahrenheit_to_celsius + +class TestTemperatureConverter(unittest.TestCase): + """Test the temperature conversion functions""" + + # Tests for celsius_to_fahrenheit + def test_celsius_to_fahrenheit_zero(self): + """It should convert 0°C to 32°F""" + actual = celsius_to_fahrenheit(0) + expected = 32.0 + self.assertEqual(actual, expected) + + def test_celsius_to_fahrenheit_positive(self): + """It should convert 100°C to 212°F""" + actual = celsius_to_fahrenheit(100) + expected = 212.0 + self.assertEqual(actual, expected) + + def test_celsius_to_fahrenheit_negative(self): + """It should convert -40°C to -40°F""" + actual = celsius_to_fahrenheit(-40) + expected = -40.0 + self.assertEqual(actual, expected) + + def test_celsius_to_fahrenheit_non_numeric(self): + """It should raise a ValueError if the input is not a number""" + with self.assertRaises(ValueError): + celsius_to_fahrenheit("invalid") + + # Tests for fahrenheit_to_celsius + def test_fahrenheit_to_celsius_zero(self): + """It should convert 32°F to 0°C""" + actual = fahrenheit_to_celsius(32) + expected = 0.0 + self.assertEqual(actual, expected) + + def test_fahrenheit_to_celsius_positive(self): + """It should convert 212°F to 100°C""" + actual = fahrenheit_to_celsius(212) + expected = 100.0 + self.assertEqual(actual, expected) + + def test_fahrenheit_to_celsius_negative(self): + """It should convert -40°F to -40°C""" + actual = fahrenheit_to_celsius(-40) + expected = -40.0 + self.assertEqual(actual, expected) + + def test_fahrenheit_to_celsius_non_numeric(self): + """It should raise a ValueError if the input is not a number""" + with self.assertRaises(ValueError): + fahrenheit_to_celsius("invalid") + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/solutions/tests/test_word_frequency_counter.py b/solutions/tests/test_word_frequency_counter.py new file mode 100644 index 000000000..b7efabf64 --- /dev/null +++ b/solutions/tests/test_word_frequency_counter.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This is a test module for counting the frequency of words in a given sentence. + +Test categories: + - Standard cases: typical strings with different lengths + - Edge cases: white space input and punctuation handling + - Defensive tests: non-string inputs + +Created on 01.01.2025 +@author: Ahmad Hamed Dehzad +""" + +import unittest +from ..word_frequency_counter import word_frequency_counter + + +class TestWordFrequencyCounter(unittest.TestCase): + """Test the word_frequency_counter function""" + + def test_empty_string(self): + """It should evaluate an empty string to an empty dictionary""" + actual = word_frequency_counter("") + expected = {} + self.assertEqual(actual, expected) + + def test_single_word(self): + """It should evaluate a single word correctly""" + actual = word_frequency_counter("Hello") + expected = {"hello": 1} + self.assertEqual(actual, expected) + + def test_multiple_words(self): + """It should count the frequency of multiple words""" + actual = word_frequency_counter("Hello world hello") + expected = {"hello": 2, "world": 1} + self.assertEqual(actual, expected) + + def test_case_insensitivity(self): + """It should treat words case-insensitively""" + actual = word_frequency_counter("Apple apple APPLE") + expected = {"apple": 3} + self.assertEqual(actual, expected) + + def test_with_punctuation(self): + """It should handle punctuation by ignoring it""" + actual = word_frequency_counter("Hello, world! Hello.") + expected = {"hello": 2, "world": 1} # Updated expected result + self.assertEqual(actual, expected) + + def test_numbers_in_string(self): + """It should handle strings with numbers""" + actual = word_frequency_counter("123 123 456") + expected = {"123": 2, "456": 1} + self.assertEqual(actual, expected) + + def test_non_string_input(self): + """It should raise a ValueError if the input is not a string""" + with self.assertRaises(ValueError): + word_frequency_counter(12345) + + def test_with_whitespace(self): + """It should handle extra spaces correctly""" + actual = word_frequency_counter(" Hello world ") + expected = {"hello": 1, "world": 1} + self.assertEqual(actual, expected) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/solutions/word_frequency_counter.py b/solutions/word_frequency_counter.py new file mode 100644 index 000000000..97e644c51 --- /dev/null +++ b/solutions/word_frequency_counter.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A module for counting the frequency of words in a given sentence. + +Features: + - Handles case-insensitivity + - Strips punctuation + - Validates input as a string + +Created on 01.01.2025 +@author: Ahmad Hamed Dehzad + +Examples: + >>> word_frequency_counter("Hello world hello") + {'hello': 2, 'world': 1} + + >>> word_frequency_counter("Hello, world! Hello.") + {'hello': 2, 'world': 1} +""" + +import string + + +def word_frequency_counter(sentence: str) -> dict[str, int]: + """ + Counts the frequency of each word in a given sentence. + + Args: + sentence (str): A sentence containing words. + + Returns: + dict[str, int]: A dictionary where keys are words (case-insensitive) + and values are their frequency. + + Raises: + ValueError: If the input is not a string. + """ + if not isinstance(sentence, str): + raise ValueError("Invalid input: Please enter a text string.") + + # Remove punctuation and split into words + cleaned_sentence = sentence.translate(str.maketrans("", "", string.punctuation)) + words = cleaned_sentence.split() + + # Count word frequencies + word_frequencies = {} + for word in words: + word = word.lower() + word_frequencies[word] = word_frequencies.get(word, 0) + 1 + + return word_frequencies