diff --git a/README.md b/README.md index 7c05260f..ec4fb722 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,120 @@ # Python Data Structures and Algorithms -No non-sense solutions to common Data Structure and Algorithm interview questions in Python. Follows a consistent approach throughout problems. +Clean, well-documented implementations of common data structures and algorithms in Python. This repository follows a consistent approach throughout all implementations, making it ideal for learning and interview preparation. -## Objective +## Table of Contents -There are a plenty of resources when it comes to interview preparations on the internet. What prompted me to create this project was the dissimilarity across different approaches and the infused complexity of the code. +- [Overview](#overview) +- [Python Version Compatibility](#python-version-compatibility) +- [Installation](#installation) +- [Usage](#usage) +- [Structure of the Repository](#structure-of-the-repository) + - [Data Structures](#data-structures) + - [Algorithms](#algorithms) + - [Bookmarks](#bookmarks) +- [Code Style and Documentation](#code-style-and-documentation) +- [Contributing](#contributing) +- [Future Improvements](#future-improvements) +- [License](#license) -Feel free to contribute but please follow the Contributing Guidelines as I want to maintain the uniformity of the implementation of data structures and algorithms. Last time around, people bombarded with me with Pull Requests, Issues and Emails insisting me to merge their changes +## Overview -The open source community has helped me a lot during my interview preparations and studies while I was in my undergrad. I always wanted to give something back to the community. In my endeavour to contribute something back, I will be uploading data structures and algorithms questions in Python in this repo. Feel free to contribute and get in touch! +This repository contains implementations of various data structures and algorithms in Python. The code is designed to be: -## Structure of the repository +- **Clean and readable**: Following PEP 8 style guidelines +- **Well-documented**: With comprehensive docstrings and comments +- **Educational**: Focusing on clarity rather than optimization +- **Interview-friendly**: Covering common interview questions and patterns -As of now, the repository contains 3 main directories: [**Bookmarks**](bookmarks), [**Data Structures**](data_structures) and [**Algorithms**](algorithms). +## Python Version Compatibility -### Data Structures +The code in this repository is compatible with **Python 3.6+**. It uses modern Python features such as: + +- Type hints (PEP 484) +- F-strings (Python 3.6+) +- Modern class definitions (no need for explicit inheritance from object) +- Up-to-date import conventions + +## Installation + +Clone the repository to your local machine: + +```bash +git clone https://github.com/prabhupant/python-ds.git +cd python-ds +``` + +No additional dependencies are required to run the code examples. + +## Usage + +Each implementation can be run directly as a Python script. Most files include test cases that demonstrate how to use the implementation. + +For example, to run the activity selection algorithm: + +```bash +python algorithms/greedy/activity_selection.py +``` + +You can also import the implementations into your own code: + +```python +from data_structures.stack.stack_using_linked_list import Stack, Element + +# Create a new stack +stack = Stack() -Contains all data structure questions categorised into sub-directories like stack, queue, etc according to their type. +# Push elements onto the stack +stack.push(Element(1)) +stack.push(Element(2)) -1. [Array](data_structures/array) -2. [Dictionary]() -3. [Binary Search Tree](data_structures/bst) -4. [Linked List](data_structures/linked_list) -5. [Stack](data_structures/stack) -6. [Graphs](data_structures/graphs) -7. [Circular Linked List](data_structures/circular_linked_list) -8. [Doubly Linked List](data_structures/doubly_linked_list) +# Pop an element from the stack +element = stack.pop() +print(element.value) # Output: 2 +``` + +## Structure of the Repository + +The repository is organized into three main directories: + +### Data Structures + +Contains implementations of various data structures, categorized by type: + +1. [Array](data_structures/array) - Array manipulations and operations +2. [Binary Search Tree](data_structures/bst) - BST implementations and operations +3. [Binary Trees](data_structures/binary_trees) - Binary tree algorithms +4. [Circular Linked List](data_structures/circular_linked_list) - Circular linked list implementations +5. [Deque](data_structures/deque) - Double-ended queue implementations +6. [Doubly Linked List](data_structures/doubly_linked_list) - Doubly linked list implementations +7. [Fenwick Tree](data_structures/fenwick_tree) - Binary indexed tree implementations +8. [Graphs](data_structures/graphs) - Graph algorithms and representations +9. [Hash](data_structures/hash) - Hash table implementations +10. [Heap](data_structures/heap) - Min and max heap implementations +11. [Linked List](data_structures/linked_list) - Singly linked list implementations +12. [Matrix](data_structures/matrix) - Matrix operations +13. [Palindromic Tree](data_structures/palindromic_tree) - Specialized tree for palindromes +14. [Queue](data_structures/queue) - Queue implementations +15. [Segment Tree](data_structures/segment_tree) - Segment tree implementations +16. [Stack](data_structures/stack) - Stack implementations +17. [Strings](data_structures/strings) - String manipulation algorithms +18. [Trie](data_structures/trie) - Trie implementations +19. [Union Find](data_structures/union_find) - Disjoint set data structure ### Algorithms -This directory contains various types of algorithm questions like Dynamic Programming, Sorting, Greedy, etc. The current structure of this directory is as follows: +Contains implementations of various algorithms, categorized by type: -1. [Dynamic Programming](algorithms/dynamic_programming) -2. [Graphs](algorithms/graph) -3. [Greedy](algorithms/greedy) -4. [Math](algorithms/math) -5. [Misc](algorithms/miscellaneous) -6. [Sorting](algorithms/sorting) -7. [Bit Manipulation](algorithms/bit_manipulation) +1. [Bit Manipulation](algorithms/bit_manipulation) - Bit manipulation techniques +2. [Dynamic Programming](algorithms/dynamic_programming) - DP solutions to common problems +3. [Greedy](algorithms/greedy) - Greedy algorithm implementations +4. [Math](algorithms/math) - Mathematical algorithms +5. [Miscellaneous](algorithms/miscellaneous) - Other algorithm implementations +6. [Sorting](algorithms/sorting) - Sorting algorithm implementations ### Bookmarks -You can find useful links in this repository in the different markdown files. Below is a table of contents. +Contains useful links to external resources, categorized by type: | Category | Link | | :-- | :--: | @@ -52,21 +125,57 @@ You can find useful links in this repository in the different markdown files. Be | Videos | [Click Here](bookmarks/videos.md) | | Misc. | [Click Here](bookmarks/misc.md) | -## Things need to be done +## Code Style and Documentation + +All code in this repository follows the [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guide. Each implementation includes: + +- A module-level docstring explaining the data structure or algorithm +- Function/method docstrings with parameters and return values +- Type hints for function parameters and return values +- Inline comments explaining complex logic +- Time and space complexity analysis -As you can see, the repo is still in its infancy. Here are some key things in the to-do. +Example: -1. Queue questions -2. Algorithms -3. More questions in data structures, especially for graph, circular linked list, trees, heaps and hash. +```python +def binary_search(arr: List[int], target: int) -> int: + """ + Perform binary search on a sorted array. + + Args: + arr: A sorted list of integers + target: The value to search for + + Returns: + The index of the target if found, -1 otherwise + + Time Complexity: O(log n) + Space Complexity: O(1) + """ + # Implementation details... +``` ## Contributing -Contributions are always welcomed. -Feel free to raise new issues, file new PRs. Consider giving it a star and fork this repo! +Contributions are always welcome! Please read the [Contributing Guidelines](CONTRIBUTING.md) before submitting a pull request. + +Some ways to contribute: +- Add new data structure or algorithm implementations +- Improve existing implementations +- Add test cases +- Fix bugs +- Improve documentation + +## Future Improvements + +The repository is continuously evolving. Here are some planned improvements: -To follow the guidelines, refer to [Contributing.md](CONTRIBUTING.md) +1. Add more queue implementations and examples +2. Expand the algorithms section with more common algorithms +3. Add more examples for graph algorithms, trees, heaps, and hash tables +4. Add unit tests for all implementations +5. Add visualization tools for data structures and algorithms ## License -[MIT License](LICENSE) +This project is licensed under the [MIT License](LICENSE). diff --git a/algorithms/greedy/activity_selection.py b/algorithms/greedy/activity_selection.py index 374ad563..c826acf1 100644 --- a/algorithms/greedy/activity_selection.py +++ b/algorithms/greedy/activity_selection.py @@ -1,33 +1,60 @@ -#Prints a maximum set of activities that can be done by a -#single person, one at a time -#n --> Total number of activities -#s[]--> An array that contains start time of all activities -#f[] --> An array that contains finish time of all activities +""" +Activity Selection Problem +This module implements the activity selection problem using a greedy algorithm. +The problem is to select the maximum number of activities that can be performed +by a single person, assuming that a person can only work on a single activity at a time. -def find_activities(arr): - n = len(arr) +Time Complexity: O(n log n) where n is the number of activities +Space Complexity: O(n) +""" +from typing import List, Tuple + + +def find_activities(activities: List[Tuple[int, int]]) -> List[Tuple[int, int]]: + """ + Find the maximum number of activities that can be performed by a single person. + + Args: + activities: A list of activities where each activity is represented as a tuple (start_time, end_time) + + Returns: + A list of selected activities in the order they should be performed + + Example: + >>> find_activities([(5, 9), (1, 2), (3, 4), (0, 6), (5, 7), (8, 9)]) + [(1, 2), (3, 4), (5, 7), (8, 9)] + """ + if not activities: + return [] + + n = len(activities) selected = [] - arr.sort(key = lambda x: x[1]) + # Sort activities by end time + activities.sort(key=lambda x: x[1]) i = 0 - # since it is a greedy algorithm, the first acitivity is always + # Since it is a greedy algorithm, the first activity is always # selected because it is the most optimal choice at that point - selected.append(arr[i]) + selected.append(activities[i]) for j in range(1, n): - start_time_next_activity = arr[j][0] - end_time_prev_activity = arr[i][1] + start_time_next_activity = activities[j][0] + end_time_prev_activity = activities[i][1] + # If the start time of the next activity is greater than or equal to + # the end time of the previously selected activity, select this activity if start_time_next_activity >= end_time_prev_activity: - selected.append(arr[j]) + selected.append(activities[j]) i = j return selected -arr = [[5, 9], [1, 2], [3, 4], [0, 6],[5, 7], [8, 9]] -print(find_activities(arr)) - +if __name__ == "__main__": + # Test case + activities = [(5, 9), (1, 2), (3, 4), (0, 6), (5, 7), (8, 9)] + print(f"Activities: {activities}") + print(f"Selected activities: {find_activities(activities)}") diff --git a/data_structures/stack/stack_using_linked_list.py b/data_structures/stack/stack_using_linked_list.py index 794541b5..7c5797af 100644 --- a/data_structures/stack/stack_using_linked_list.py +++ b/data_structures/stack/stack_using_linked_list.py @@ -1,15 +1,66 @@ -""" Use LinkedList class to implement a Stack. """ +""" +Stack Implementation Using Linked List -class Element(object): - def __init__(self, value): +This module implements a Stack data structure using a LinkedList. +A stack is a linear data structure that follows the Last In First Out (LIFO) principle. + +Operations: +- push: Add an element to the top of the stack +- pop: Remove the top element from the stack + +Time Complexity: +- Push: O(1) +- Pop: O(1) + +Space Complexity: O(n) where n is the number of elements in the stack +""" +from typing import Optional, Any, TypeVar + +T = TypeVar('T') # Define a type variable for generic typing + + +class Element: + """ + A class representing an element in a linked list. + + Attributes: + value: The value stored in the element + next: Reference to the next element in the linked list + """ + def __init__(self, value: Any): + """ + Initialize a new Element with a value. + + Args: + value: The value to be stored in the element + """ self.value = value - self.next = None - -class LinkedList(object): - def __init__(self, head=None): - self.head = head - - def append(self, new_element): + self.next: Optional['Element'] = None + + +class LinkedList: + """ + A class representing a singly linked list. + + Attributes: + head: Reference to the first element in the linked list + """ + def __init__(self, head: Optional[Element] = None): + """ + Initialize a new LinkedList with an optional head element. + + Args: + head: The first element of the linked list (default: None) + """ + self.head: Optional[Element] = head + + def append(self, new_element: Element) -> None: + """ + Append a new element to the end of the linked list. + + Args: + new_element: The element to append + """ current = self.head if self.head: while current.next: @@ -18,15 +69,25 @@ def append(self, new_element): else: self.head = new_element - def insert_first(self, new_element): - "Insert new element as the head of the LinkedList" - # fetch the current head + def insert_first(self, new_element: Element) -> None: + """ + Insert a new element as the head of the LinkedList. + + Args: + new_element: The element to insert at the beginning + """ + # Fetch the current head current = self.head new_element.next = current self.head = new_element - def delete_first(self): - "Delete the first (head) element in the LinkedList and return it" + def delete_first(self) -> Optional[Element]: + """ + Delete the first (head) element in the LinkedList and return it. + + Returns: + The deleted element or None if the list is empty + """ current = self.head if current: if current.next: @@ -37,34 +98,76 @@ def delete_first(self): else: return None -class Stack(object): - def __init__(self,top=None): + +class Stack: + """ + A class representing a Stack data structure implemented using a LinkedList. + + Attributes: + ll: The LinkedList used to store the stack elements + """ + def __init__(self, top: Optional[Element] = None): + """ + Initialize a new Stack with an optional top element. + + Args: + top: The element to be placed at the top of the stack (default: None) + """ self.ll = LinkedList(top) - def push(self, new_element): - "Push (add) a new element onto the top of the stack" + def push(self, new_element: Element) -> None: + """ + Push (add) a new element onto the top of the stack. + + Args: + new_element: The element to push onto the stack + """ self.ll.insert_first(new_element) - def pop(self): - "Pop (remove) the first element off the top of the stack and return it" + def pop(self) -> Optional[Element]: + """ + Pop (remove) the first element off the top of the stack and return it. + + Returns: + The element removed from the top of the stack or None if the stack is empty + """ return self.ll.delete_first() - -# Test cases -# Set up some Elements -e1 = Element(1) -e2 = Element(2) -e3 = Element(3) -e4 = Element(4) - -# Start setting up a Stack -stack = Stack(e1) - -# Test stack functionality -stack.push(e2) -stack.push(e3) -print(stack.pop().value) -print(stack.pop().value) -print(stack.pop().value) -print(stack.pop()) -stack.push(e4) -print(stack.pop().value) + + def is_empty(self) -> bool: + """ + Check if the stack is empty. + + Returns: + True if the stack is empty, False otherwise + """ + return self.ll.head is None + + +if __name__ == "__main__": + # Test cases + # Set up some Elements + e1 = Element(1) + e2 = Element(2) + e3 = Element(3) + e4 = Element(4) + + # Start setting up a Stack + stack = Stack(e1) + + # Test stack functionality + stack.push(e2) + stack.push(e3) + print(f"Pop: {stack.pop().value}") + print(f"Pop: {stack.pop().value}") + print(f"Pop: {stack.pop().value}") + + # Test empty stack + empty_result = stack.pop() + print(f"Pop from empty stack: {empty_result}") + + # Test pushing after empty + stack.push(e4) + print(f"Pop after pushing to empty stack: {stack.pop().value}") + + # Test is_empty method + print(f"Is stack empty? {stack.is_empty()}") diff --git a/data_structures/strings/unique_char_check.py b/data_structures/strings/unique_char_check.py index 3af3083c..5a3e98e5 100644 --- a/data_structures/strings/unique_char_check.py +++ b/data_structures/strings/unique_char_check.py @@ -1,24 +1,95 @@ """ -Question -You are given a string S, check if all characters are unique. - -SAMPLE INPUT 1 -abcd -SAMPLE OUTPUT 1 -True - -SAMPLE INPUT 2 -aabc -SAMPLE OUTPUT 2 -False +Unique Character Check + +This module provides a function to check if all characters in a string are unique. + +Problem: +Given a string, determine if it has all unique characters (no repeated characters). + +Examples: +- "abcd" -> True (all characters are unique) +- "aabc" -> False (character 'a' is repeated) + +Time Complexity: O(n) where n is the length of the string +Space Complexity: O(min(n, k)) where k is the size of the character set """ from collections import Counter -def unique_char_check(S): - character_count = Counter(S) +from typing import Dict, Set + + +def unique_char_check(string: str) -> bool: + """ + Check if all characters in a string are unique. + + This function uses Counter from the collections module to count + the occurrences of each character in the string. If the number of + unique characters equals the length of the string, then all characters + are unique. + + Args: + string: The input string to check + + Returns: + True if all characters in the string are unique, False otherwise + + Examples: + >>> unique_char_check("abcd") + True + >>> unique_char_check("aabc") + False + """ + # Count occurrences of each character + character_count: Dict[str, int] = Counter(string) + + # If the number of unique characters equals the length of the string, + # then all characters are unique + return len(character_count) == len(string) + + +def unique_char_check_set(string: str) -> bool: + """ + Alternative implementation using a set. + + This function uses a set to track seen characters. If a character + is encountered that's already in the set, the function returns False. + + Args: + string: The input string to check + + Returns: + True if all characters in the string are unique, False otherwise + + Examples: + >>> unique_char_check_set("abcd") + True + >>> unique_char_check_set("aabc") + False + """ + seen: Set[str] = set() + + for char in string: + if char in seen: + return False + seen.add(char) + + return True + + +if __name__ == "__main__": + # Test with user input + user_input = input("Enter a string to check for unique characters: ") + result = unique_char_check(user_input) + + print(f"Using Counter: '{user_input}' has all unique characters: {result}") + + # Also test the alternative implementation + result_set = unique_char_check_set(user_input) + print(f"Using Set: '{user_input}' has all unique characters: {result_set}") - if len(character_count) == len(S): - return True - return False + # Additional test cases + test_cases = ["abcd", "aabc", "", "a", "abcdefghijklmnopqrstuvwxyz"] -S = input() -print(unique_char_check(S)) \ No newline at end of file + print("\nAdditional test cases:") + for test in test_cases: + result = unique_char_check(test) + print(f"'{test}': {result}")