[
  {
    "path": ".gitignore",
    "content": "/__pycache__/\n/venv/\n/.idea/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Ivan Markovic\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Problem-Solving-with-Algorithms-and-Data-Structures-using-Python\n\nI started the project by learning data structures and algorithms from a book *Problem Solving with Algorithms and Data Structures using Python*.\nIt does not contain everything from the book, \nnor is everything implemented in the same way,\nbut it also contains other data structures, algorithms and problems.\n\n[Book website](https://runestone.academy/runestone/books/published/pythonds/index.html)\n\n[Videos](https://teklern.blogspot.com/p/blog-page.html)\n\n### [Algorithm analysis](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/analysis)\n- [Anagrams - quadratic solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-quadratic-solution.py)\n- [Anagrams - log linear solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-loglinear-solution.py)\n- [Anagrams - linear solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-linear-solution.py)\n- [Time - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/time-iterative-approach.py)\n- [Time - non-iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/time-noniterative-approach.py)\n\n### [Stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/stack)\n- [Stack - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-array-impl.py)\n- [Stack - less efficient array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-array-impl-less-efficient.py)\n- [Stack - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-fixed-size-array-impl.py)\n- [Stack two queues](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack_two_queues.py)\n- [Stack - linked list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-linked-list-impl.py)\n    - [Examples](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/stack/examples)\n        - [Balanced brackets](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/examples/balanced-brackets.py)\n        - [Number converter](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/examples/number_converter.py)\n       \n\n### [Queue](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/queue)\n- [Queue - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-array-impl.py)\n- [Queue - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-fixed-size-array-impl.py)\n- [Queue - linked list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-linked-list-impl.py)\n- [Circular queue - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/circular-queue-fixed-size-array-impl.py)\n- [Queue - two stacks implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-two-stacks-impl.py)\n\n### [Deque](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/deque)\n- [Deque - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/deque.py)\n- [Circular deque - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/circular-deque.py)\n- [Deque - linked list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/deque_linked_list_impl.py)\n\n### [Linked lists](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists)\n- [Singly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/singly-linked-list)\n- [Doubly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/doubly-linked-list)\n- [Circular singly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/circular-singly-linked-list)\n- [Circular doubly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/circular-doubly-linked-list)\n\n### [Recursion](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/recursion)\n- [Convert number - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/convert-number.py)\n- [Convert number - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/convert-number-iterative.py)\n- [Factorial](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/factorial.py)\n- [Fibonacci - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-iterative.py)\n- [Fibonacci - recursive worst solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-recursive-worst-solution.py)\n- [Fibonacci - memoization](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-memoization.py)\n- [Fibonacci - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-recursive.py)\n- [Fibonacci sum recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-sum-recursive.py)\n- [Fibonacci sum iterative](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-sum-iterative.py)\n- [Maze - path finder](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/maze.py)\n- [Palindrome](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/palindrome.py)\n- [Reverse linked list - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-linked-list.py)\n- [Reverse linked list - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-linked-list-iterative.py)\n- [Reverse linked list - iterative solution stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-linked-list-iterative-stack.py)\n- [Reverse list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-list.py)\n- [Reverse string](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-string.py)\n- [Sum numbers - binary recursion](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/sum-numbers-binary-recursion.py)\n- [Sum numbers - recursion with pointer](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/sum-numbers-pointer.py)\n- [Sum numbers - recursion with slicing](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/sum-numbers-slicing.py)\n- [Towers of Hanoi](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/towers-of-hanoi.py)\n\n### [Searching](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/searching)\n- [Sequential search - unordered list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/sequential-search-unordered-list.py)\n- [Sequential search - ordered list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/sequential-search-ordered-list.py)\n- [Binary search iterative](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-iterative.py)\n- [Binary search recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-recursive.py)\n- [Binary search recursive pointers](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-recursive-pointers.py)\n\n### [Hash table](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/hash-table)\n- [Linear probing](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/hash-table/linear-probing.py)\n- [Chaining](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/hash-table/chaining.py)\n\n### [Trees](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trees)\n- [List of lists representation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/list-representation.py)\n- [Class representation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/class-representation.py)\n- [Parse tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/parse-tree.py)\n- [Tree traversal](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trees/tree-traversal)\n    - [Methods](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/treenode.py)\n    - [Functions](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/functions.py)\n    - [Preorder traversal example](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/preorder-traversal-example.py)\n    - [Inorder traversal example](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/inorder-traversal-example.py)\n    - [Postorder traversal example](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/postorder-traversal-example.py)\n- [Binary Heap - min heap implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/binary-heap.py)  \n- [Binary Search Tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/binary-search-tree.py)\n- [AVL tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/avl-tree.py)\n\n### [Sorting](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/sorting)\n- [Bubble sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/bubble-sort.py)\n- [Short bubble](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/short-bubble.py)\n- [Insertion sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/insertion-sort.py)\n- [Selection sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/selection-sort.py)\n- [Merge sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/merge-sort.py)\n- [Quicksort in place](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/quicksort.py)\n- [Quicksort - return new array](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/quicksort-return-new-array.py)\n\n### [Graphs](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs)\n- [Breadth first search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/breadth-first-search)\n- [Depth first search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search)\n    - [Depth first search - stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search/depth-first-search)\n    - [Depth first search - recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search/depth-first-search-recursive)\n- [Cycle detection](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection)\n    - [Cycle detection directed graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection/cycle-directed-graph)\n    - [Cycle detection undirected graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection/cycle-undirected-graph)\n- [Topological sorting](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/topological-sorting)\n- [Dijkstra algorithm - Shortest path](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra)\n    - [Priority Queue implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/priority-queue-impl-adjacency-map)\n    - [Matrix implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/matrix-impl)\n    - [Adjacency list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/adjacency-list-impl)\n- [Bellman-Ford algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/bellman-ford)\n- [Bellman-Ford algorithm - negative weight cycle](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/bellman-ford-negative-weight-cycle)\n- [Minimum Spanning Tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree)\n    - [Minimum Spanning Tree in undirected graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree/breadth-first-search)\n    - [Prim's algorithm - Minimum Spanning Tree in undirected weighted graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree/prims-algorithm)\n    - [Kruskal's algorithm - Minimum Spanning Tree in undirected weighted graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree/kruskals-algorithm)\n- [Is graph bipartite](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/is-graph-bipartite)\n- [Union find](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find)\n    - [Number of connected components](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find/number-of-connected-components)\n    - [Union find path compression](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find/union-find-path-compression)\n- [Kosaraju's algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/kosarajus-algorithm)\n### [Trie](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trie)\n- [Trie](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trie)\n\n\n### [Substring search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/substring-search)\n- [Brute force algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/substring-search/brute_force.py)\n"
  },
  {
    "path": "analysis/anagrams-linear-solution.py",
    "content": "\n# O(n)\ndef anagrams(string1: str, string2: str) -> bool:\n    if len(string1) != len(string2):\n        return False\n\n    is_anagram: bool = True\n    list1: list = [0] * 26\n    list2: list = [0] * 26\n\n    count_characters(string1, list1)\n    count_characters(string2, list2)\n    pos: int = 0\n    while pos < len(list1) and is_anagram: # 26 steps max\n        if list1[pos] != list2[pos]:\n            is_anagram = False\n        else:\n            pos += 1\n\n    return is_anagram\n\n\ndef count_characters(string: str, arr: list):\n    for character in string:\n        index: int = ord(character) - ord('a')\n        arr[index] += 1\n\n\nprint(anagrams(\"python\", \"typhon\"))\nprint(anagrams(\"abba\", \"baba\"))\nprint(anagrams(\"abba\", \"abbb\"))\n\n"
  },
  {
    "path": "analysis/anagrams-loglinear-solution.py",
    "content": "\n# O(nlogn)\ndef anagrams(string1: str, string2: str) -> bool:\n\n    if len(string1) != len(string2):\n        return False\n\n    is_anagram: bool = True\n    list1: list = list(string1)\n    list2: list = list(string2)\n    list1.sort() # sorting is O(nlogn)\n    list2.sort() # sorting is O(nlogn)\n    pos: int = 0\n    # loop is O(n)\n    while pos < len(list1) and is_anagram:\n        if list1[pos] != list2[pos]:\n            is_anagram = False\n        else:\n            pos += 1\n\n    return is_anagram\n\n\nprint(anagrams(\"python\", \"typhon\"))\nprint(anagrams(\"abba\", \"baba\"))\nprint(anagrams(\"abba\", \"abbb\"))\n\n"
  },
  {
    "path": "analysis/anagrams-quadratic-solution.py",
    "content": "\n# O(n2)\ndef anagrams(string1: str, string2: str) -> bool:\n    if len(string1) != len(string2):\n        return False\n\n    is_anagram: bool = True\n\n    list2: list = list(string2)\n    pos1: int = 0\n    while pos1 < len(string1) and is_anagram:\n        character = string1[pos1]\n        character_found: bool = False\n        pos2: int = 0\n        while pos2 < len(list2) and not character_found:\n            if list2[pos2] == character:\n                list2[pos2] = None\n                character_found = True\n            else:\n                pos2 += 1\n        if not character_found:\n            is_anagram = False\n        else:\n            pos1 += 1\n\n    return is_anagram\n\n\nprint(anagrams(\"python\", \"typhon\"))\nprint(anagrams(\"abba\", \"baba\"))\nprint(anagrams(\"abba\", \"abbb\"))\n\n"
  },
  {
    "path": "analysis/time-iterative-approach.py",
    "content": "import time\n\n\ndef sum_nums(n: int) -> int:\n    start = time.time()\n    total: int = 0\n    for i in range(n + 1):\n        total += i\n    end = time.time()\n    print(\"For \", n, \" numbers time is\", end - start)\n    return total\n\n\nnums: list = [100, 10000, 100000, 1000000]\nfor n in nums:\n    print(sum_nums(n))\n\n"
  },
  {
    "path": "analysis/time-noniterative-approach.py",
    "content": "import time\n\n\ndef sum_nums(n: int) -> int:\n    start = time.time()\n    total: int = n * (n + 1) / 2\n    end = time.time()\n    print(\"For \", n, \" numbers time is\", end - start)\n    return total\n\n\nnums: list = [100, 10000, 100000, 1000000]\nfor n in nums:\n    print(sum_nums(n))\n\n"
  },
  {
    "path": "deque/circular-deque.py",
    "content": "\nfrom typing import Any, List\n\n\nclass Deque:\n\n    def __init__(self, capacity: int = 10) -> None:\n        self.capacity: int = capacity\n        self.length: int = 0\n        self._deque: List[Any] = [None] * self.capacity\n        self.front: int = -1\n        self.rear: int = -1\n\n    def is_empty(self) -> bool:\n        return self.front == -1\n\n    def is_full(self) -> bool:\n        return (self.rear + 1) % self.capacity == self.front\n\n    def size(self) -> int:\n        return self.length\n\n    def add_front(self, item: Any):\n        if self.is_full():\n            raise Exception('Deque is full')\n        if self.is_empty(): # self.rear == -1\n            self.front = self.rear = 0\n        elif self.front == 0:\n            self.front = self.capacity - 1\n        else:\n            self.front -= 1\n        self._deque[self.front] = item\n        self.length += 1\n\n    def add_rear(self, item: Any):\n        if self.is_full():\n            raise Exception('Deque is full')\n        if self.is_empty(): # self.rear == -1\n            self.front = self.rear = 0\n        else:\n            self.rear = (self.rear + 1) % self.capacity\n        self._deque[self.rear] = item\n        self.length += 1\n\n    def remove_front(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        item: Any = self._deque[self.front]\n        if self.front == self.rear:\n            self.front = self.rear = -1\n        else:\n            self.front = (self.front + 1) % self.capacity\n        self.length -= 1\n        return item\n\n    def remove_rear(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        item: Any = self._deque[self.rear]\n        if self.rear == self.front:\n            self.rear = self.front = -1\n        elif self.rear == 0:\n            self.rear = self.capacity - 1\n        else:\n            self.rear -= 1\n        self.length -= 1\n        return item\n\n    def get_front(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        return self._deque[self.front]\n\n    def get_rear(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        return self._deque[self.rear]\n\n\ndef is_palindrome(string:str='') -> bool:\n    d: Deque = Deque()\n\n    for character in string:\n        d.add_rear(character)\n\n    palindrome: bool = True\n    while d.size() > 1 and palindrome:\n        if d.remove_front() != d.remove_rear():\n            palindrome = False\n    \n    return palindrome\n\nprint(is_palindrome('radar'))\nprint(is_palindrome('radr'))\nprint(is_palindrome('r'))\nprint(is_palindrome(''))\n"
  },
  {
    "path": "deque/deque.py",
    "content": "\nfrom typing import Any, List\n\n\nclass Deque:\n\n    def __init__(self) -> None:\n        self._deque: List[Any] = []\n\n    def is_empty(self) -> bool:\n        return len(self._deque) == 0\n\n    def size(self) -> int:\n        return len(self._deque)\n\n    def add_front(self, item: Any):\n        self._deque.insert(0, item)\n\n    def add_rear(self, item: Any):\n        self._deque.append(item)\n\n    def remove_front(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        return self._deque.pop(0)\n\n    def remove_rear(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        return self._deque.pop()\n\n\n\ndef is_palindrome(string:str='') -> bool:\n    d: Deque = Deque()\n\n    for character in string:\n        d.add_rear(character)\n\n    palindrome: bool = True\n    while d.size() > 1 and palindrome:\n        if d.remove_front() != d.remove_rear():\n            palindrome = False\n    \n    return palindrome\n\nprint(is_palindrome('radar'))\nprint(is_palindrome('radr'))\nprint(is_palindrome('r'))\nprint(is_palindrome(''))\n"
  },
  {
    "path": "deque/deque_linked_list_impl.py",
    "content": "\n\nfrom typing import List, Any\n\n\nclass ListNode:\n\n    def __init__(self, key:Any = None, prev:'ListNode' = None, next:'ListNode' = None):\n        self.key:Any = key\n        self.prev:'ListNode' = prev\n        self.next:'ListNode' = next\n\n\nclass Deque:\n\n    def __init__(self):\n        self._head:ListNode = None\n        self._tail:ListNode = None\n        self._length:int = 0\n\n\n    def size(self) -> int:\n        return self._length\n    \n\n    def is_empty(self) -> bool:\n        return self._length == 0\n    \n\n    def add_front(self, e:Any) -> None:\n        if self.is_empty():\n            self._head = self._tail = ListNode(e)\n        else:\n            self._head = ListNode(e, None, self._head)\n            self._head.next.prev = self._head\n        self._length += 1\n\n\n    def add_rear(self, e:Any) -> None:\n        if self.is_empty():\n            self._head = self._tail = ListNode(e)\n        else:\n            self._tail.next = ListNode(e, self._tail, None)\n            self._tail = self._tail.next\n        self._length += 1\n\n\n    def remove_front(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        e:Any = self._head.key\n        if self._head == self._tail:\n            self._head = self._tail = None\n        else:\n            self._head = self._head.next\n            self._head.prev = None\n        self._length -= 1\n        return e\n    \n\n    def remove_rear(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        e:Any = self._tail.key\n        if self._head == self._tail:\n            self._head = self._tail = None\n        else:\n            self._tail = self._tail.prev\n            self._tail.next = None\n        self._length -= 1\n        return e\n    \n\n    def get_front(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        return self._head.key\n    \n\n    def remove_rear(self) -> Any:\n        if self.is_empty():\n            raise Exception('Deque is empty')\n        return self._tail.key\n"
  },
  {
    "path": "graphs/bellman-ford/graph.py",
    "content": "\n#  Bellman-Ford algorithm\n#  Find shortest paths from one vertex,\n#  to all other vertices in weighted graph.\n#  Runtime O(V*E)\n\nfrom typing import Set, Dict, List, Tuple\n\n\nclass Graph:\n\n\n    def __init__(self) -> None:\n        self.vertices:Set[str] = set()\n        self.edges:List[Tuple[str, str, int]] = list()\n        self.prev:Dict[str, str] = dict()\n        self.distances:Dict[str, int] = dict()\n\n\n    def add_vertex(self, label:str) -> None:\n        self.vertices.add(label)\n        self.prev[label] = None\n        self.distances[label] = None\n\n\n    def add_edge(self, v1:str, v2:str, distance:int) -> None:\n        self.edges.append((v1, v2, distance))\n\n\n    def bellman_ford(self, label:str) -> None:\n        self.distances[label] = 0\n\n        for _ in range(len(self.vertices) - 1):\n\n            for v1, v2, distance in self.edges:\n                if self.distances[v1] is None:\n                    continue\n                if self.distances[v2] is None or self.distances[v2] > self.distances[v1] + distance:\n                    self.distances[v2] = self.distances[v1] + distance\n                    self.prev[v2] = v1\n\n        # Check for negative-weight cycles\n        for v1, v2, distance in self.edges:\n            if self.distances[v1] is not None and self.distances[v2] > self.distances[v1] + distance:\n                raise ValueError(\"Graph contains a negative-weight cycle\")\n\n        self._print_paths(label)\n\n\n    def _print_paths(self, label:str) -> None:\n        for v in self.vertices:\n            if v == label:\n                continue\n            if self.distances[v] is not None:\n                print(f'Path from {label} to {v} is {self._return_path(v)} and distance is {self.distances[v]}')\n            else:\n                print(f'No path from {label} to {v}')\n\n\n    def _return_path(self, label:str) -> str:\n        if self.prev[label] is None:\n            return label\n        return self._return_path(self.prev[label]) + ' -> ' + label\n    \n\n\ng = Graph()\nfor v in ['A', 'B', 'C', 'D']:\n    g.add_vertex(v)\n\ng.add_edge('A', 'B', 1)\ng.add_edge('B', 'C', 3)\ng.add_edge('A', 'C', 10)\ng.add_edge('C', 'D', 2)\ng.add_edge('D', 'B', 4)\n\ng.bellman_ford('A')\n\n"
  },
  {
    "path": "graphs/bellman-ford-negative-weight-cycle/graph.py",
    "content": "\n#  Bellman-Ford algorithm\n#  Find shortest paths from one vertex,\n#  to all other vertices in weighted graph.\n#  Runtime O(V*E)\n\n# Negative weight cycle will be found with one more loop through edges\n# If there is a need to update distance than it is a negative weight cycle\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.edges: list = []\n        self.distance: dict = {}\n        self.prev: dict = {}\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.distance[label] = None\n        self.prev[label] = None\n\n    def add_edge(self, label1: str, label2: str, weight: int):\n        self.edges.append([label1, label2, weight])\n\n    def bellman_ford(self, source: str):\n        self.distance[source] = 0\n\n        for _ in range(len(self.vertices)):\n\n            for edge in self.edges:\n                label1: str = edge[0]\n                label2: str = edge[1]\n                weight: int = edge[2]\n\n                if self.distance[label1] is None:\n                    continue\n                if self.distance[label2] is None:\n                    self.distance[label2] = self.distance[label1] + weight\n                    self.prev[label2] = label1\n                    continue\n                if self.distance[label1] + weight < self.distance[label2]:\n                    self.distance[label2] = self.distance[label1] + weight\n                    self.prev[label2] = label1\n                    continue\n\n        for edge in self.edges:\n            label1: str = edge[0]\n            label2: str = edge[1]\n            weight: int = edge[2]\n\n            if self.distance[label1] is None:\n                continue\n            if self.distance[label2] is None:\n                continue\n            if self.distance[label1] + weight < self.distance[label2]:\n                print(f'Negative weight cycle from {label1} to {label2}')\n\n    def print_distances(self, source: str):\n        for v in self.vertices:\n            if v != source:\n                distance: int = self.distance[v]\n                print(f'Distance from {source} to {v} is {distance}')"
  },
  {
    "path": "graphs/breadth-first-search/graph.py",
    "content": "from queue import Queue\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.prev: dict = {}\n        self.distance: dict = {}\n        self.colors: dict = {}\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.prev[label] = None\n        self.distance[label] = 0\n        self.colors[label] = \"white\"\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacency_list[label1].append(label2)\n        self.adjacency_list[label2].append(label1)\n\n    def bfs(self, label: str):\n        q: Queue = Queue()\n        q.enqueue(label)\n        self.colors[label] = \"gray\"\n        while not q.is_empty():\n            tmp: str = q.dequeue()\n            for neighbour in self.adjacency_list[tmp]:\n                if self.colors[neighbour] == \"white\":\n                    self.prev[neighbour] = tmp\n                    self.distance[neighbour] = self.distance[tmp] + 1\n                    self.colors[neighbour] = \"gray\"\n                    q.enqueue(neighbour)\n            self.colors[tmp] = \"black\"\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n"
  },
  {
    "path": "graphs/breadth-first-search/main.py",
    "content": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor i in range(len(my_vertices)):\n    graph.add_vertex(my_vertices[i])\n\ngraph.add_edge('A', 'B')\ngraph.add_edge('A', 'C')\ngraph.add_edge('A', 'D')\ngraph.add_edge('C', 'D')\ngraph.add_edge('C', 'G')\ngraph.add_edge('D', 'G')\ngraph.add_edge('D', 'H')\ngraph.add_edge('B', 'E')\ngraph.add_edge('B', 'F')\ngraph.add_edge('E', 'I')\n\ngraph.bfs(\"A\")\nprint(graph.return_path(\"H\"))\n"
  },
  {
    "path": "graphs/breadth-first-search/queue.py",
    "content": "class Queue:\n    def __init__(self):\n        self.queue = []\n\n    def enqueue(self, item):\n        self.queue.insert(0, item)\n\n    def dequeue(self):\n        return self.queue.pop()\n\n    def size(self):\n        return len(self.queue)\n\n    def is_empty(self):\n        return self.queue == []"
  },
  {
    "path": "graphs/cycle-detection/Cycle.md",
    "content": "\n\n#Cycle - vertex is reachable from itself. \n\n\n##Undirected graph\n\n'''\nedge(u, v)\n'''\n\n- v is in the stack, visited but not explored\n\n- v is ancestor of u, but not parent\n\n##Directed graph\n\n'''\nedge(u, v)\n'''\n\n- v is in the stack, visited but not explored\n\n- v is ancestor of u"
  },
  {
    "path": "graphs/cycle-detection/cycle-directed-graph/graph.py",
    "content": "\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.prev: dict = {}\n        self.distance: dict = {}\n        self.colors: dict = {}\n        self.entry: dict = {}\n        self.exit: dict = {}\n        self.time: int = 0\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.prev[label] = None\n        self.distance[label] = 0\n        self.colors[label] = \"white\"\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacency_list[label1].append(label2)\n\n    def dfs(self, label: str):\n        self.colors[label] = \"gray\"\n        self.time += 1\n        self.entry[label] = self.time\n        for neighbour in self.adjacency_list[label]:\n            if self.colors[neighbour] == \"white\":\n                self.prev[neighbour] = label\n                self.distance[neighbour] = self.distance[label] + 1\n                self.dfs(neighbour)\n            if self.colors[neighbour] == \"gray\":\n                print(\"Cycle\", label, \" - \", neighbour)\n        self.colors[label] = \"black\"\n        self.time += 1\n        self.exit[label] = self.time\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n"
  },
  {
    "path": "graphs/cycle-detection/cycle-directed-graph/main.py",
    "content": "from graph import Graph\n\ngraph: Graph = Graph()\n\n\nvertices = [\"a\", \"b\", \"c\", \"d\"]\nfor vertex in vertices:\n    graph.add_vertex(vertex)\n\ngraph.add_edge(\"a\", \"b\")\ngraph.add_edge(\"b\", \"c\")\ngraph.add_edge(\"c\", \"d\")\ngraph.add_edge(\"d\", \"b\")\n\ngraph.dfs(\"a\")\n\n"
  },
  {
    "path": "graphs/cycle-detection/cycle-undirected-graph/graph.py",
    "content": "\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.prev: dict = {}\n        self.distance: dict = {}\n        self.colors: dict = {}\n        self.entry: dict = {}\n        self.exit: dict = {}\n        self.time: int = 0\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.prev[label] = None\n        self.distance[label] = 0\n        self.colors[label] = \"white\"\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacency_list[label1].append(label2)\n        self.adjacency_list[label2].append(label1)\n\n    def dfs(self, label: str):\n        self.colors[label] = \"gray\"\n        self.time += 1\n        self.entry[label] = self.time\n        for neighbour in self.adjacency_list[label]:\n            if self.colors[neighbour] == \"white\":\n                self.prev[neighbour] = label\n                self.distance[neighbour] = self.distance[label] + 1\n                self.dfs(neighbour)\n            if self.colors[neighbour] == \"gray\" and self.prev[label] != neighbour:\n                print(\"Cycle\", label, \" - \", neighbour)\n        self.colors[label] = \"black\"\n        self.time += 1\n        self.exit[label] = self.time\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n"
  },
  {
    "path": "graphs/cycle-detection/cycle-undirected-graph/main.py",
    "content": "from graph import Graph\n\ngraph: Graph = Graph()\n\n\nvertices = [\"a\", \"b\", \"c\", \"d\"]\nfor vertex in vertices:\n    graph.add_vertex(vertex)\n\ngraph.add_edge(\"a\", \"b\")\ngraph.add_edge(\"b\", \"c\")\ngraph.add_edge(\"c\", \"d\")\n# graph.add_edge(\"d\", \"b\")\n\ngraph.dfs(\"a\")\n\n"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search/graph.py",
    "content": "from stack import Stack\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.prev: dict = {}\n        self.distance: dict = {}\n        self.colors: dict = {}\n        self.entry: dict = {}\n        self.exit: dict = {}\n        self.time: int = 0\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.prev[label] = None\n        self.distance[label] = 0\n        self.colors[label] = \"white\"\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacency_list[label1].append(label2)\n        self.adjacency_list[label2].append(label1)\n\n    def dfs(self, label: str):\n        s: Stack = Stack()\n        s.push(label)\n        self.colors[label] = \"gray\"\n        self.time += 1\n        self.entry[label] = self.time\n        while not s.is_empty():\n            tmp: str = s.peek()\n            neighbour: str = self.find_unvisited_neighbour(tmp)\n            if neighbour is not None:\n                self.prev[neighbour] = tmp\n                self.distance[neighbour] = self.distance[tmp] + 1\n                self.colors[neighbour] = \"gray\"\n                self.time += 1\n                self.entry[neighbour] = self.time\n                s.push(neighbour)\n            else:\n                s.pop()\n                self.time += 1\n                self.exit[tmp] = self.time\n                self.colors[tmp] = \"black\"\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n\n    def find_unvisited_neighbour(self, tmp) -> str:\n        for n in self.adjacency_list[tmp]:\n            if self.colors[n] == \"white\":\n                return n\n        return None\n"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search/main.py",
    "content": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor i in range(len(my_vertices)):\n    graph.add_vertex(my_vertices[i])\n\ngraph.add_edge('A', 'B')\ngraph.add_edge('A', 'C')\ngraph.add_edge('A', 'D')\ngraph.add_edge('C', 'D')\ngraph.add_edge('C', 'G')\ngraph.add_edge('D', 'G')\ngraph.add_edge('D', 'H')\ngraph.add_edge('B', 'E')\ngraph.add_edge('B', 'F')\ngraph.add_edge('E', 'I')\n\ngraph.dfs(\"A\")\nprint(graph.return_path(\"H\"))\n"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search/stack.py",
    "content": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n    def push(self, item):\n        self.items.append(item)\n\n    def pop(self):\n        return self.items.pop()\n\n    def peek(self):\n        return self.items[len(self.items) - 1]\n\n    def size(self):\n        return len(self.items)"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search-recursive/graph.py",
    "content": "\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.prev: dict = {}\n        self.distance: dict = {}\n        self.colors: dict = {}\n        self.entry: dict = {}\n        self.exit: dict = {}\n        self.time: int = 0\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.prev[label] = None\n        self.distance[label] = 0\n        self.colors[label] = \"white\"\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacency_list[label1].append(label2)\n        self.adjacency_list[label2].append(label1)\n\n    def dfs(self, label: str):\n        self.colors[label] = \"gray\"\n        self.time += 1\n        self.entry[label] = self.time\n        for neighbour in self.adjacency_list[label]:\n            if self.colors[neighbour] == \"white\":\n                self.prev[neighbour] = label\n                self.distance[neighbour] = self.distance[label] + 1\n                self.dfs(neighbour)\n        self.colors[label] = \"black\"\n        self.time += 1\n        self.exit[label] = self.time\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search-recursive/main.py",
    "content": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor i in range(len(my_vertices)):\n    graph.add_vertex(my_vertices[i])\n\ngraph.add_edge('A', 'B')\ngraph.add_edge('A', 'C')\ngraph.add_edge('A', 'D')\ngraph.add_edge('C', 'D')\ngraph.add_edge('C', 'G')\ngraph.add_edge('D', 'G')\ngraph.add_edge('D', 'H')\ngraph.add_edge('B', 'E')\ngraph.add_edge('B', 'F')\ngraph.add_edge('E', 'I')\n\ngraph.dfs(\"A\")\nprint(graph.return_path(\"H\"))\n"
  },
  {
    "path": "graphs/dijkstra/adjacency-list-impl/main.py",
    "content": "\nfrom typing import List, Dict, Set\n\nfrom vertex import Vertex\n\n\nclass Graph:\n\n    def __init__(self, capacity :int =10):\n        self.capacity :int = capacity\n        self.vertices :Dict[str, Vertex] = dict()\n        self.prev :Dict[str, str] = dict()\n        self.adjacency_list :Dict[str, List[Vertex]] = dict()\n        self.visited :Set[str] = set()\n\n\n\n    def add_vertex(self, label :str, weight :int = float('inf')) -> None:\n        v :Vertex = Vertex(label, weight)\n        self.vertices[v.label] = v\n        self.adjacency_list[label] = list()\n        self.prev[label] = None\n\n\n    def add_edge(self, label1 :str, label2 :str, weight :int) -> None:\n        v :Vertex = Vertex(label2, weight)\n        self.adjacency_list[label1].append(v)\n\n\n    def dijkstra(self, label :str) -> None:\n        v :Vertex = self.vertices[label]\n        v.weight = 0\n\n        while v is not None:\n            for n in self.adjacency_list[v.label]:\n                o :Vertex = self.vertices[n.label]\n                if v.weight + n.weight < o.weight:\n                    o.weight = v.weight + n.weight\n                    self.prev[o.label] = v.label\n            self.visited.add(v.label)\n            v = self._find_cheapest_vertex()\n\n\n    def show_path(self, label :str) -> str:\n        if self.prev[label] is None:\n            return label\n        return self.show_path(self.prev[label]) + '->' + label\n\n\n    def _find_cheapest_vertex(self) -> Vertex:\n        vertex :Vertex = None\n        for v in self.vertices.values():\n            if v.label not in self.visited:\n                if vertex is None:\n                    vertex = v\n                elif vertex.weight > v.weight:\n                    vertex = v\n\n        return vertex\n\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"START\")\ngraph.add_vertex(\"A\")\ngraph.add_vertex(\"C\")\ngraph.add_vertex(\"B\")\ngraph.add_vertex(\"D\")\ngraph.add_vertex(\"END\")\n\ngraph.add_edge(\"START\", \"A\", 0)\ngraph.add_edge(\"START\", \"C\", 2)\ngraph.add_edge(\"A\", \"B\", 18)\ngraph.add_edge(\"A\", \"D\", 15)\ngraph.add_edge(\"C\", \"B\", 3)\ngraph.add_edge(\"C\", \"D\", 10)\ngraph.add_edge(\"B\", \"END\", 150)\ngraph.add_edge(\"D\", \"END\", 15)\ngraph.dijkstra(\"START\")\n\nprint(graph.show_path(\"END\"))\n\n\n\n\n\n"
  },
  {
    "path": "graphs/dijkstra/adjacency-list-impl/vertex.py",
    "content": "\n\nclass Vertex:\n\n    def __init__(self, label :str, weight :int = float('inf')):\n        self.label :str = label\n        self.weight :int = weight"
  },
  {
    "path": "graphs/dijkstra/matrix-impl/graph.py",
    "content": "from vertex import Vertex\n\n\nclass Graph:\n    def __init__(self, size: int = 10):\n        self.size: int = size\n        self.index: int = 0\n        self.vertices_list: list = [None] * self.size\n        self.vertices: dict = {}\n        self.adjacency_matrix: list = [[None for i in range(self.size)] for j in range(self.size)]\n        self.prev: dict = {}\n        self.visited: dict = {}\n\n    def add_vertex(self, label: str):\n        if self.index == self.size:  # matrix is full\n            return\n        vertex: Vertex = Vertex(label, float(\"inf\"), self.index)\n        self.vertices_list[self.index] = vertex\n        self.vertices[vertex.label] = vertex\n        self.index += 1\n        self.prev[vertex.label] = None\n        self.visited[vertex.label] = False\n\n    def add_edge(self, label1: str, label2: str, weight: int):\n        index1: int = self.vertices[label1].index\n        index2: int = self.vertices[label2].index\n        self.adjacency_matrix[index1][index2] = weight\n\n    def dijkstra(self, label: str):\n        current_vertex: Vertex = self.vertices[label]\n        current_vertex.weight = 0\n        while current_vertex is not None:\n            self.visited[current_vertex.label] = True\n            for i in range(self.index):\n                if self.adjacency_matrix[current_vertex.index][i] is not None:\n                    weight: int = self.adjacency_matrix[current_vertex.index][i]\n                    neighbour: Vertex = self.vertices_list[i]\n                    if current_vertex.weight + weight < neighbour.weight:\n                        neighbour.weight = current_vertex.weight + weight\n                        self.prev[neighbour.label] = current_vertex.label\n            current_vertex = self.find_minimum_weight_vertex()\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n\n    def find_minimum_weight_vertex(self):\n        vertex: Vertex = None\n        for label in self.vertices:\n            if not self.visited[label]:\n                if vertex is None:\n                    vertex = self.vertices[label]\n                else:\n                    if vertex.weight > self.vertices[label].weight:\n                        vertex = self.vertices[label]\n        return vertex\n\n\n\n"
  },
  {
    "path": "graphs/dijkstra/matrix-impl/main.py",
    "content": "from graph import Graph\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"START\")\ngraph.add_vertex(\"A\")\ngraph.add_vertex(\"C\")\ngraph.add_vertex(\"B\")\ngraph.add_vertex(\"D\")\ngraph.add_vertex(\"END\")\n\ngraph.add_edge(\"START\", \"A\", 0)\ngraph.add_edge(\"START\", \"C\", 2)\ngraph.add_edge(\"A\", \"B\", 18)\ngraph.add_edge(\"A\", \"D\", 15)\ngraph.add_edge(\"C\", \"B\", 3)\ngraph.add_edge(\"C\", \"D\", 10)\ngraph.add_edge(\"B\", \"END\", 150)\ngraph.add_edge(\"D\", \"END\", 15)\ngraph.dijkstra(\"START\")\n\nprint(graph.return_path(\"END\"))\n"
  },
  {
    "path": "graphs/dijkstra/matrix-impl/vertex.py",
    "content": "class Vertex:\n    def __init__(self, label: str  = None, weight: int = float(\"inf\"), index: int = None):\n        self.label: str = label\n        self.weight: int = weight\n        self.index: int = index\n\n"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/graph.py",
    "content": "from vertex import Vertex\nfrom priorityqueue import PriorityQueue\n\n\nclass Graph:\n    def __init__(self):\n        self._vertices: dict = {}\n        self._adjacency_map: dict = {}\n        self._prev: dict = {}\n\n    def add_vertex(self, label: str):\n        v: Vertex = Vertex(label)\n        self._vertices[label] = v\n        self._adjacency_map[label]: list = []\n        self._prev[label] = None\n\n    def add_edge(self, label1: str, label2: str, weight: int):\n        self._adjacency_map[label1].append(Vertex(label2, weight))\n\n    def dijkstra(self, label: str):\n        self._vertices[label].weight = 0\n        pq: PriorityQueue = PriorityQueue()\n        for label in self._vertices:\n            pq.insert(self._vertices[label])\n        while not pq.is_empty():\n            current: Vertex = pq.delete_min()\n            for neighbour in self._adjacency_map[current.label]:\n                v: Vertex = self._vertices[neighbour.label]\n                if current.weight + neighbour.weight < v.weight:\n                    v.weight = current.weight + neighbour.weight\n                    self._prev[v.label] = current.label\n                    pq.decrease_key(v.key)\n\n    def show_path(self, label: str) -> str:\n        if self._prev[label] is None:\n            return label\n        else:\n            return self.show_path(self._prev[label]) + \" -> \" + label\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/main.py",
    "content": "from graph import Graph\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"START\")\ngraph.add_vertex(\"A\")\ngraph.add_vertex(\"C\")\ngraph.add_vertex(\"B\")\ngraph.add_vertex(\"D\")\ngraph.add_vertex(\"END\")\n\ngraph.add_edge(\"START\", \"A\", 0)\ngraph.add_edge(\"START\", \"C\", 2)\ngraph.add_edge(\"A\", \"B\", 18)\ngraph.add_edge(\"A\", \"D\", 15)\ngraph.add_edge(\"C\", \"B\", 3)\ngraph.add_edge(\"C\", \"D\", 10)\ngraph.add_edge(\"B\", \"END\", 150)\ngraph.add_edge(\"D\", \"END\", 15)\ngraph.dijkstra(\"START\")\n\nprint(graph.show_path(\"END\"))\n\n"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/priorityqueue.py",
    "content": "from vertex import Vertex\n\n\nclass PriorityQueue:\n    def __init__(self):\n        self.pq: list = [None]\n        self._pointer: int = 0\n\n    def is_empty(self) -> bool:\n        return self._pointer == 0\n\n    def insert(self, vertex: Vertex):\n        self.pq.append(vertex)\n        self._pointer += 1\n        vertex.key = self._pointer\n        self._perc_up(self._pointer)\n\n    def _perc_up(self, pointer: int):\n        while pointer // 2 > 0:\n            if self.pq[pointer // 2].weight > self.pq[pointer].weight:\n                self.pq[pointer // 2], self.pq[pointer] = self.pq[pointer], self.pq[pointer // 2]\n                self.pq[pointer // 2].key = pointer // 2\n                self.pq[pointer].key = pointer\n            pointer = pointer // 2\n\n    def decrease_key(self, pointer: int):\n        self._perc_up(pointer)\n\n    def delete_min(self) -> Vertex:\n        if self.is_empty():\n            raise Exception(\"Priority queue is empty\")\n        v: Vertex = self.pq[1]\n        self.pq[1] = self.pq[self._pointer]\n        self.pq[1].key = 1\n        self.pq.pop()\n        self._pointer -= 1\n        self._perc_down(1)\n        return v\n\n    def _perc_down(self, pointer: int):\n        while pointer * 2 <= self._pointer:\n            min_index: int = self._find_swap_index(pointer)\n            if self.pq[pointer].weight > self.pq[min_index].weight:\n                self.pq[pointer], self.pq[min_index] = self.pq[min_index], self.pq[pointer]\n                self.pq[pointer].key = pointer\n                self.pq[min_index].key = min_index\n            pointer = min_index\n\n    def _find_swap_index(self, pointer: int) -> int:\n        if pointer * 2 + 1 > self._pointer:\n            return pointer * 2\n        else:\n            if self.pq[pointer * 2].weight <= self.pq[pointer * 2 + 1].weight:\n                return pointer * 2\n            else:\n                return pointer * 2 + 1\n"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/vertex.py",
    "content": "class Vertex:\n    def __init__(self, label: str = None, weight: int = float(\"inf\"), key: int = None):\n        self.label: str = label\n        self.weight: int = weight\n        self.key: int = key\n"
  },
  {
    "path": "graphs/is-graph-bipartite/graph.py",
    "content": "from queue import Queue\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.color: dict = {}\n\n    def add_vertex(self, label: str = None):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.color[label] = None\n\n    def add_edge(self, label1: str = None, label2: str = None):\n        self.adjacency_list[label1].append(label2)\n        self.adjacency_list[label2].append(label1)\n\n    def bipartite_check(self) -> bool:\n        for vertex in self.vertices:\n            if self.color[vertex] is not None:\n                continue\n            q: Queue = Queue()\n            self.color[vertex] = \"red\"\n            q.enqueue(vertex)\n            while not q.is_empty():\n                tmp: str = q.dequeue()\n                for neighbour in self.adjacency_list[tmp]:\n                    if self.color[neighbour] == self.color[tmp]:\n                        return False\n                    if self.color[neighbour] is None:\n                        if self.color[tmp] == \"red\":\n                            self.color[neighbour] = \"blue\"\n                        else:\n                            self.color[neighbour] = \"red\"\n                        q.enqueue(neighbour)\n        return True\n\n\n\n\n\n\n"
  },
  {
    "path": "graphs/is-graph-bipartite/main.py",
    "content": "from graph import Graph\n\n\ng: Graph = Graph()\ng.add_vertex(\"a\")\ng.add_vertex(\"b\")\ng.add_vertex(\"c\")\ng.add_vertex(\"d\")\n\n# a, b ||---|| c, d\n\n\ng.add_edge(\"a\", \"c\")\ng.add_edge(\"a\", \"d\")\n\ng.add_edge(\"b\", \"c\")\ng.add_edge(\"b\", \"d\")\n\n\n# g.add_edge(\"a\", \"b\")\n\nprint(g.bipartite_check())\n"
  },
  {
    "path": "graphs/is-graph-bipartite/queue.py",
    "content": "class Queue:\n    def __init__(self):\n        self._queue: list = []\n\n    def is_empty(self) -> bool:\n        return len(self._queue) == 0\n\n    def enqueue(self, vertex: str):\n        self._queue.append(vertex)\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        return self._queue.pop(0)\n"
  },
  {
    "path": "graphs/kosarajus-algorithm/graph.py",
    "content": "\n\nfrom typing import Dict, List, Set\n\nfrom stack import Stack\n\nclass Graph:\n\n\n    def __init__(self) -> None:\n        self.vertices:Set[str] = set()\n        self.adjacency_list:Dict[str, Set[str]] = dict()\n        self.adjacency_list_reversed:Dict[str, Set[str]] = dict()\n        self.visited:Set[str] = set()\n        self.stack:Stack = Stack()\n\n\n    def add_vertex(self, label:str) -> None:\n        self.vertices.add(label)\n        self.adjacency_list[label] = set()\n        self.adjacency_list_reversed[label] = set()\n\n\n    def add_edge(self, label1:str, label2:str) -> None:\n        if label1 not in self.vertices or label2 not in self.vertices:\n            raise Exception('Vertices are not added')\n        self.adjacency_list[label1].add(label2)\n        self.adjacency_list_reversed[label2].add(label1)\n\n\n    def kosaraju(self) -> List[List[str]]:\n        for v in self.vertices:\n            if v not in self.visited:\n                self._dfs(v)\n\n        self.visited.clear()\n\n        connected_components:List[List[str]] = list()\n\n        while not self.stack.is_empty():\n            v:str = self.stack.pop()\n            if v not in self.visited:\n                connected:List[str] = list()\n                self._dfs_reversed(v, connected)\n\n                if len(connected) > 0:\n                    connected_components.append(connected)\n\n        return list(connected_components)\n\n\n\n    def _dfs(self, label:str) -> None:\n        self.visited.add(label)\n\n        for n in self.adjacency_list[label]:\n            if n not in self.visited:\n                self._dfs(n)\n\n        self.stack.push(label)\n\n\n    def _dfs_reversed(self, v:str, connected:List[str]) -> None:\n        connected.append(v)\n        self.visited.add(v)\n        for n in self.adjacency_list_reversed[v]:\n            if n not in self.visited:\n                self._dfs_reversed(n, connected)\n\n\n\n\n"
  },
  {
    "path": "graphs/kosarajus-algorithm/main.py",
    "content": "from graph import Graph\n\ng: Graph = Graph()\nfor i in range(9):\n    g.add_vertex(str(i))\n\ng.add_edge('0', '1')\ng.add_edge('1', '2')\ng.add_edge('2', '3')\ng.add_edge('3', '0')\ng.add_edge('2', '4')\ng.add_edge('4', '5')\ng.add_edge('5', '6')\ng.add_edge('6', '4')\ng.add_edge('7', '6')\ng.add_edge('8', '7')\n\nprint(g.kosaraju())\n"
  },
  {
    "path": "graphs/kosarajus-algorithm/stack.py",
    "content": "class Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len(self._stack) == 0\n\n    def push(self, item):\n        self._stack.append(item)\n\n    def pop(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        # return self._stack[-1] # python way\n        return self._stack[len(self._stack) - 1]\n\n    def size(self) -> int:\n        return len(self._stack)"
  },
  {
    "path": "graphs/minimum-spanning-tree/breadth-first-search/graph.py",
    "content": "from queue import Queue\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        self.prev: dict = {}\n        self.distance: dict = {}\n        self.colors: dict = {}\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacency_list[label]: list = []\n        self.prev[label] = None\n        self.distance[label] = 0\n        self.colors[label] = \"white\"\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacency_list[label1].append(label2)\n        self.adjacency_list[label2].append(label1)\n\n    def minimum_spanning_tree(self, label: str) -> list:  # this is breadth first search\n        min_edges: list = []\n        q: Queue = Queue()\n        q.enqueue(label)\n        self.colors[label] = \"gray\"\n        while not q.is_empty():\n            tmp: str = q.dequeue()\n            for neighbour in self.adjacency_list[tmp]:\n                if self.colors[neighbour] == \"white\":\n                    min_edges.append([tmp, neighbour])\n                    self.prev[neighbour] = tmp\n                    self.distance[neighbour] = self.distance[tmp] + 1\n                    self.colors[neighbour] = \"gray\"\n                    q.enqueue(neighbour)\n            self.colors[tmp] = \"black\"\n        return min_edges\n\n    def return_path(self, label: str) -> str:\n        if self.prev[label] is None:\n            return label\n        else:\n            return self.return_path(self.prev[label]) + \" -> \" + label\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/breadth-first-search/main.py",
    "content": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor i in range(len(my_vertices)):\n    graph.add_vertex(my_vertices[i])\n\ngraph.add_edge('A', 'B')\ngraph.add_edge('A', 'C')\ngraph.add_edge('A', 'D')\ngraph.add_edge('C', 'D')\ngraph.add_edge('C', 'G')\ngraph.add_edge('D', 'G')\ngraph.add_edge('D', 'H')\ngraph.add_edge('B', 'E')\ngraph.add_edge('B', 'F')\ngraph.add_edge('E', 'I')\n\nedges: list = graph.minimum_spanning_tree(\"A\")\nprint(edges)\nprint(graph.return_path(\"H\"))\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/breadth-first-search/queue.py",
    "content": "class Queue:\n    def __init__(self):\n        self.queue = []\n\n    def enqueue(self, item):\n        self.queue.insert(0, item)\n\n    def dequeue(self):\n        return self.queue.pop()\n\n    def size(self):\n        return len(self.queue)\n\n    def is_empty(self):\n        return self.queue == []"
  },
  {
    "path": "graphs/minimum-spanning-tree/kruskals-algorithm/graph.py",
    "content": "\nfrom typing import List, Set, Dict, Tuple\n\nclass Graph:\n\n\n    def __init__(self) -> None:\n        self.vertices:Set[str] = set()\n        self.roots:Dict[str, str] = dict()\n        self.sizes:Dict[str, int] = dict()\n        self.edges:List[Tuple[str, str, int]] = list()\n        self.mst:List[Tuple[str, str, int]] = list()\n\n    \n    def add_vertex(self, label:str) -> None:\n        self.vertices.add(label)\n        self.roots[label] = label\n        self.sizes[label] = 1\n\n\n    def add_edge(self, label1:str, label2:str, weight:int) -> None:\n        if label1 not in self.vertices or label2 not in self.vertices:\n            raise Exception(\"Vertices must be added before connecting them\")\n        self.edges.append((label1, label2, weight))\n\n\n    def kruskal(self) -> List[Tuple[str, str, int]]:\n        self.mst.clear()\n        self.edges.sort(key = lambda edge: edge[2])\n\n        for v1, v2, weight in self.edges:\n\n            root1:str = self._find_root(v1)\n            root2:str = self._find_root(v2)\n\n            if root1 != root2:\n                if self.sizes[root1] >= self.sizes[root2]:\n                    self.roots[root2] = root1\n                    self.sizes[root1] += self.sizes[root2]\n                else:\n                    self.roots[root1] = root2\n                    self.sizes[root2] += self.sizes[root1]\n                self.mst.append((v1, v2, weight))\n\n        return list(self.mst)\n    \n    def _find_root(self, label:str) -> str:\n        if self.roots[label] != label:\n            self.roots[label] = self._find_root(self.roots[label])\n        return self.roots[label]\n\n\n\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/kruskals-algorithm/main.py",
    "content": "from graph import Graph\n\ng: Graph = Graph()\n\ng.add_vertex('a')\ng.add_vertex('b')\ng.add_vertex('d')\ng.add_vertex('c')\ng.add_vertex('e')\ng.add_vertex('f')\n\ng.add_edge('a', 'f', 4)\ng.add_edge('a', 'b', 23)\n\ng.add_edge('b', 'd', 17)\ng.add_edge('b', 'c', 3)\ng.add_edge('c', 'd', 41)\n\ng.add_edge('c', 'f', 2)\ng.add_edge('f', 'e', 1)\n\nprint(g.kruskal())\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/graph.py",
    "content": "\nfrom typing import Dict, List\n\nfrom vertex import Vertex\nfrom priorityqueue import PriorityQueue\n\nclass Graph:\n\n    def __init__(self) -> None:\n        self.vertices: Dict[str, Vertex] = dict()\n        self.adjacency_map: Dict[str, List[Vertex]] = dict()\n        self.prev:Dict[str, str] = dict()\n\n\n    def add_vertex(self, label:str, weight:int=float('inf')):\n        self.vertices[label] = Vertex(label)\n        self.adjacency_map[label] = []\n        self.prev[label] = None\n\n\n    def add_edge(self, label1:str, label2:str, weight:int):\n        self.adjacency_map[label1].append(Vertex(label2, weight))\n        self.adjacency_map[label2].append(Vertex(label1, weight))\n\n    \n    def prims(self, label:str):\n        res:str = ''\n        v:Vertex = self.vertices[label]\n        v.weight = 0\n\n        pq:PriorityQueue = PriorityQueue()\n        for k in self.vertices:\n            vertex = self.vertices[k]\n            pq.insert(vertex)\n\n        while not pq.is_empty():\n            v = pq.delete_min()\n            print('Min is ', v.label)\n            if self.prev[v.label] is not None:\n                res += f'{self.prev[v.label]} -> {v.label}, '\n            for neighbour in self.adjacency_map[v.label]:\n                vertex:Vertex = self.vertices[neighbour.label]\n                if neighbour.weight < vertex.weight:\n                    vertex.weight = neighbour.weight\n                    self.prev[vertex.label] = v.label\n                    pq.decrease_key(vertex.index)\n\n        print(res)\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/main.py",
    "content": "from graph import Graph\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"a\")\ngraph.add_vertex(\"b\")\ngraph.add_vertex(\"f\")\ngraph.add_vertex(\"c\")\ngraph.add_vertex(\"d\")\ngraph.add_vertex(\"e\")\n\ngraph.add_edge(\"a\", \"b\", 4)\ngraph.add_edge(\"a\", \"f\", 2)\ngraph.add_edge(\"b\", \"c\", 6)\ngraph.add_edge(\"f\", \"b\", 3)\ngraph.add_edge(\"f\", \"c\", 1)\ngraph.add_edge(\"f\", \"e\", 4)\ngraph.add_edge(\"c\", \"d\", 3)\ngraph.add_edge(\"d\", \"e\", 2)\ngraph.prims(\"a\")  # a -> f, f -> c, f -> b, c -> d, d -> e,\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/priorityqueue.py",
    "content": "from typing import List\n\nfrom vertex import Vertex\n\n\nclass PriorityQueue:\n\n\n    def __init__(self) -> None:\n        self.queue: List[Vertex] = [None]\n        self.pointer:int = 0\n\n\n    def is_empty(self) -> bool:\n        return self.pointer == 0\n\n\n    def insert(self, v:Vertex):\n        self.queue.append(v)\n        self.pointer += 1\n        v.index = self.pointer\n        self.perc_up(self.pointer)\n\n\n    def perc_up(self, index:int):\n        while index // 2 > 0:\n            if self.queue[index].weight < self.queue[index // 2].weight:\n                self.queue[index], self.queue[index // 2] = self.queue[index // 2], self.queue[index]\n                self.queue[index].index = index \n                self.queue[index // 2].index = index // 2\n            index = index // 2\n\n\n    def decrease_key(self, key:int):\n        self.perc_up(key)\n\n\n    def get_min(self) -> Vertex:\n        if self.is_empty():\n            raise Exception('Priority queue is empty')\n        return self.queue[1]\n\n\n    def delete_min(self) -> Vertex:\n        if self.is_empty():\n            raise Exception('Priority queue is empty')\n        v:Vertex = self.queue[1]\n        self.queue[1] = self.queue[self.pointer]\n        self.queue[1].index = 1\n        self.queue.pop()\n        self.pointer -= 1\n        self.perc_down(1)\n        return v\n\n\n    def perc_down(self, index:int):\n        while index * 2 <= self.pointer:\n            min_index:int = self.find_min_index(index)\n            if self.queue[index].weight > self.queue[min_index].weight:\n                self.queue[index], self.queue[min_index] = self.queue[min_index], self.queue[index]\n                self.queue[min_index].index = min_index \n                self.queue[index].index = index\n            index = min_index\n\n\n\n    def find_min_index(self, index:int) -> int:\n        if index * 2 + 1 > self.pointer:\n            return index * 2\n        else:\n            if self.queue[index * 2].weight <= self.queue[index * 2 + 1].weight:\n                return index * 2\n            else:\n                return index * 2 + 1\n\n"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/vertex.py",
    "content": "class Vertex:\n\n    def __init__(self, label:str=None, weight:int=float('inf'), index:int=None) -> None:\n        self.label:str = label\n        self.weight:int = weight\n        self.index:int = index\n\n"
  },
  {
    "path": "graphs/topological-sorting/graph.py",
    "content": "class Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacencyList: dict = {}\n        self.colors: dict = {}\n        self.previous: dict = {}\n        self.distance: dict = {}\n        self.time: int = 0\n        self.entry: dict = {}\n        self.exit: dict = {}\n        self.no_incoming_edges: dict = {} # to avoid looking through whole list\n        self.explored: list = []\n\n    def add_vertex(self, label: str):\n        self.vertices.append(label)\n        self.adjacencyList[label]: list = []\n        self.colors[label] = \"white\"\n        self.distance[label] = 0\n        self.previous[label] = None\n        self.no_incoming_edges[label] = label\n\n    def add_edge(self, label1: str, label2: str):\n        self.adjacencyList[label1].append(label2)\n        if label2 in self.no_incoming_edges:\n            del self.no_incoming_edges[label2]  # remove vertex, it has incoming edge\n\n    def topsort(self):\n        # perform depth first search on vertices with no incoming edges\n        for label in self.no_incoming_edges:\n            self.dfs(label)\n        self.explored.reverse()\n        print(self.explored)\n\n    def dfs(self, start: str):\n        self.time += 1\n        self.entry[start] = self.time\n        self.colors[start] = \"gray\"\n        for neighbour in self.adjacencyList[start]:\n            if self.colors[neighbour] == \"white\":\n                self.previous[neighbour] = start\n                self.distance[neighbour] = self.distance[neighbour] + 1\n                self.dfs(neighbour)\n        self.time += 1\n        self.exit[start] = self.time\n        self.colors[start] = \"black\"\n        self.explored.append(start)\n\n    def show_path(self, label: str) -> str:\n        if self.previous[label] is None:\n            return label\n        else:\n            return self.show_path(self.previous[label]) + \" -> \" + label\n\n"
  },
  {
    "path": "graphs/topological-sorting/main.py",
    "content": "from graph import Graph\n\ngraph = Graph()\n\nvertices = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"]\nfor vertex in vertices:\n    graph.add_vertex(vertex)\n\n\ngraph.add_edge(\"a\", \"c\")\ngraph.add_edge(\"c\", \"d\")\ngraph.add_edge(\"c\", \"f\")\ngraph.add_edge(\"b\", \"c\")\ngraph.add_edge(\"b\", \"e\")\n\ngraph.topsort()\n\n"
  },
  {
    "path": "graphs/union-find/number-of-connected-components/graph.py",
    "content": "from typing import List\n\ndef find_parent(i: int, components: List[int]) -> int:\n    \n    while i != components[i]:\n        i = components[i]\n    return i\n\n\nclass Graph:\n    def number_of_connected_components(self, edges_matrix: List[List[int]]) -> int:\n        \n        n: int = len(edges_matrix)\n            \n        number_of_components: int = n\n            \n        components: List[int] = [None] * n\n            \n        for i in range(n):\n            components[i] = i\n            \n        for i in range(n):\n            \n            for j in range(n):\n                \n                if edges_matrix[i][j] == 1:\n                    \n                    parent_one: int = find_parent(i, components)\n                    parent_two: int = find_parent(j, components)\n                        \n                    if parent_one != parent_two:\n                        components[parent_one] = parent_two\n                        number_of_components -= 1\n                        \n        return number_of_components"
  },
  {
    "path": "graphs/union-find/union-find-path-compression/graph.py",
    "content": "from vertex import Vertex\n\n\nclass Graph:\n\n    def __init__(self):\n        self.vertices: dict = {}\n        self.edges: list = []\n\n    def add_vertex(self, label: str = None):\n        self.vertices[label] = Vertex(label)\n\n    def add_edge(self, label1: str, label2: str):\n        self.edges.append([label1, label2])\n\n    def union_find(self):\n\n        for edge in self.edges:\n            label_one: str = edge[0]\n            label_two: str = edge[1]\n            vertex_one = self.vertices[label_one]\n            vertex_two = self.vertices[label_two]\n\n            root_one: Vertex = self.find_root(vertex_one)\n            root_two: Vertex = self.find_root(vertex_two)\n\n            if root_one != root_two:\n                if root_one.rank > root_two.rank:\n                    root_two.parent = root_one\n                    root_one.rank = root_one.rank + 1\n                else:\n                    root_one.parent = root_two\n                    root_two.rank = root_two.rank + 1\n\n    def find_root(self, vertex: Vertex):\n        if vertex.parent != vertex:\n            vertex.parent = self.find_root(vertex.parent)\n            return vertex.parent\n        return vertex.parent\n"
  },
  {
    "path": "graphs/union-find/union-find-path-compression/main.py",
    "content": "from graph import Graph\nfrom vertex import Vertex\n\ng: Graph = Graph()\ng.add_vertex('a')\ng.add_vertex('b')\ng.add_vertex('c')\ng.add_vertex('d')\ng.add_vertex('e')\ng.add_vertex('f')\n\ng.add_edge('a', 'b')\ng.add_edge('c', 'd')\ng.add_edge('d', 'e')\ng.add_edge('c', 'f')\ng.add_edge('f', 'b')\n\ng.union_find()\n\nfor label in g.vertices:\n    vertex: Vertex = g.vertices[label]\n    print(f'{vertex.label} parent is {vertex.parent.label}, rank is {vertex.parent.rank}')\n\n\n\n\n"
  },
  {
    "path": "graphs/union-find/union-find-path-compression/vertex.py",
    "content": "class Vertex:\n\n    def __init__(self, label: str = None):\n        self.label = label\n        self.parent: 'Vertex' = self\n        self.rank: int = 0\n\n"
  },
  {
    "path": "hash-table/chaining.py",
    "content": "\n\nfrom typing import Any, List\n\nclass KeyValue:\n\n    def __init__(self, key: int, value: Any) -> None:\n        self.key: int = key\n        self.value: Any = value\n\n\nclass HashTable:\n\n\n    def __init__(self, capacity:int = 11) -> None:\n        self.capacity: int = capacity\n        self.length: int = 0\n        self.table: List[List[KeyValue]] = [None] * self.capacity\n\n    \n    def put(self, key: int, value: Any) -> int:\n        index: int = self.hash(key)\n        if self.table[index] is None:\n            self.table[index] = [KeyValue(key, value)]\n            self.length += 1\n        else:\n            found: bool = False\n            i: int = 0\n            items: List[KeyValue] = self.table[index]\n            while i < len(items) and not found:\n                if items[i].key == key:\n                    found = True\n                else:\n                    i += 1\n            if found:\n                items[i].value = value\n            else:\n                items.append(KeyValue(key, value))\n                self.length += 1\n\n\n    def get(self, key: int) -> Any:\n        index: int = self.hash(key)\n        if self.table[index] is None:\n            return None\n        else:\n            found: bool = False\n            i: int = 0\n            items: List[KeyValue] = self.table[index]\n            while i < len(items) and not found:\n                if items[i].key == key:\n                    found = True\n                else:\n                    i += 1\n            if found:\n                return items[i].value\n            else:\n                return None\n\n\n    def contains(self, key: int) -> bool:\n        index: int = self.hash(key)\n        if self.table[index] is None:\n            return False\n        else:\n            found: bool = False\n            i: int = 0\n            items: List[KeyValue] = self.table[index]\n            while i < len(items) and not found:\n                if items[i].key == key:\n                    found = True\n                else:\n                    i += 1\n            if found:\n                return True\n            else:\n                return False\n\n\n    def delete(self, key: int) -> None:\n        index: int = self.hash(key)\n        if self.table[index] is None:\n            return None\n        else:\n            found: bool = False\n            i: int = 0\n            items: List[KeyValue] = self.table[index]\n            while i < len(items) and not found:\n                if items[i].key == key:\n                    found = True\n                else:\n                    i += 1\n            if not found:\n                return None\n            \n            items.pop(i)\n            if len(items) == 0:\n                self.table[index] = None\n            return None\n\n\n    def hash(self, key: int) -> int:\n        return key % self.capacity\n\n\n    def size(self) -> int:\n        return self.length\n\n\n\nht: HashTable = HashTable()\nht.put(11, 'string 11')\nht.put(22, 'string 22')\nht.put(33, 'string 33')\nht.put(44, 'string 44')\n\nht.put(21, 'string 21')\nht.put(12, 'string 12')\n\nprint(ht.size())\nprint('Get 11', ht.get(11))\nprint('Get 33', ht.get(33))\nprint('Get 147', ht.get(147))\nprint('----------------------------------------')\n\nprint('Contains 22', ht.contains(22))\nht.delete(22)\nprint(ht.size())\nprint('Contains 22', ht.contains(22))\nprint('----------------------------------------')\n\nprint('Contains 77', ht.contains(77))\nht.put(44, 'string 144')\nht.put(77, 'string 77')\n\nprint(ht.size())\nprint('Contains 77', ht.contains(77))\n"
  },
  {
    "path": "hash-table/linear-probing.py",
    "content": "\n\nfrom typing import Any, List, Tuple\n\n\nclass HashTable:\n\n    def __init__(self, capacity:int = 11) -> None:\n        self.capacity:int  = capacity\n        self.keys: List[int] = [None] * self.capacity\n        self.values: List[int] = [None] * self.capacity\n        self.length: int = 0\n\n\n    def put(self, key:int, value:Any):\n        index, contains = self.find(key)\n        if contains:\n            self.values[index] = value\n            return\n        hash:int = self.hash(key)\n        if self.keys[hash] == float('inf') or self.keys[hash] is None:\n            self.keys[hash] = key \n            self.values[hash] = value\n            self.length += 1\n        else:\n            new_hash:int = self.rehash(hash) % self.capacity\n            while self.keys[new_hash] is not None and self.keys[new_hash] != float('inf') and new_hash != hash:\n                new_hash = self.rehash(new_hash)\n            if self.keys[new_hash] == float('inf') or self.keys[new_hash] is None:\n                self.keys[new_hash] = key \n                self.values[new_hash] = value\n                self.length += 1\n\n        \n    def contains(self, key:int) -> bool:\n        _, contains = self.find(key)\n        return contains\n\n\n    def get(self, key:int) -> Any:\n        index, contains = self.find(key)\n        if contains:\n            return self.values[index] \n        return None\n\n\n    def delete(self, key:int):\n        index, contains = self.find(key)\n        if not contains:\n            return\n        self.keys[index] = float('inf')\n        self.values[index] = None\n        self.length -= 1\n\n\n    def find(self, key:int) -> Tuple[int, bool]:\n        hash:int = self.hash(key)\n        if self.keys[hash] == key:\n            return (hash, True)\n        elif self.keys[hash] is None:\n            return (None, False)\n        else:\n            new_hash:int = self.rehash(hash) % self.capacity\n            while self.keys[new_hash] != key and self.keys[new_hash] is not None and new_hash != hash:\n                new_hash = self.rehash(new_hash)\n\n            if self.keys[new_hash] == key:\n                return (new_hash, True)\n            return (None, False)\n\n\n    def size(self) -> int:\n        return self.length\n\n    def hash(self, key:int) -> int:\n        return key % self.capacity\n\n\n    def rehash(self, old_hash:int) -> int:\n        return (old_hash + 1) % self.capacity\n\n\nht: HashTable = HashTable()\nht.put(11, 'string 11')\nht.put(22, 'string 22')\nht.put(33, 'string 33')\nht.put(44, 'string 44')\n\nht.put(21, 'string 21')\nht.put(12, 'string 12')\n\nprint(ht.keys)\nprint(ht.values)\nprint(ht.size())\nprint('Get 11', ht.get(11))\nprint('Get 22', ht.get(22))\nprint('Get 147', ht.get(147))\nprint('----------------------------------------')\n\nprint('Contains 22', ht.contains(22))\nht.delete(22)\nprint(ht.size())\nprint(ht.keys)\nprint(ht.values)\nprint('Contains 22', ht.contains(22))\nprint('----------------------------------------')\n\nprint('Contains 44', ht.contains(44))\nprint(ht.keys)\nprint(ht.values)\nprint('Contains 77', ht.contains(77))\nht.put(44, 'string 144')\nht.put(77, 'string 77')\n\nprint(ht.size())\nprint(ht.keys)\nprint(ht.values)\nprint('Contains 77', ht.contains(77))\nprint('Contains 44', ht.contains(44))\n\n"
  },
  {
    "path": "linked-lists/circular-doubly-linked-list/list.py",
    "content": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = None\n\n    def is_empty(self) -> bool:\n        return self.head is None\n\n    def print_all(self):\n        if self.is_empty():\n            return\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            print(tmp.key, end=\", \")\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n        print(\"\")\n\n    def number_of_elements(self) -> int:\n        if self.is_empty():\n            return 0\n        count: int = 0\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            count += 1\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n        return count\n\n    def add_to_head(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n            self.head.prev = self.tail\n            self.tail.next = self.head\n        else:\n            self.head = Node(key, self.tail, self.head)\n            self.head.next.prev = self.head\n            self.tail.next = self.head\n\n    def add_to_tail(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n            self.head.prev = self.tail\n            self.tail.next = self.head\n        else:\n            self.tail.next = Node(key, self.tail, self.head)\n            self.tail = self.tail.next\n            self.head.prev = self.tail\n\n    def delete_from_head(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.head\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                self.head = self.head.next\n                self.head.prev = self.tail\n                self.tail.next = self.head\n            return ret_node.key\n\n    def delete_from_tail(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.tail\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                self.tail = self.tail.prev\n                self.tail.next = self.head\n                self.head.prev = self.tail\n            return ret_node.key\n\n    def delete_nodes_with_value(self, key: int):\n        if self.is_empty():\n            return None\n        ret_value: int = None\n        tmp: Node = self.head\n        while tmp.next != self.head:\n            if tmp.next.key == key:\n                ret_value = tmp.next.key\n                tmp.next = tmp.next.next\n                tmp.next.prev = tmp\n            else:\n                tmp = tmp.next\n        self.tail = tmp\n        if self.head.key == key:\n            ret_value = self.delete_from_head()\n        return ret_value\n\n    def delete_on_index(self, index: int):\n        if self.is_empty():\n            return\n        end_index: int = self.number_of_elements() - 1\n        if index < 0 or index > end_index:\n            return\n        if index == 0:\n            self.delete_from_head()\n        elif index == end_index:\n            self.delete_from_tail()\n        else:\n            tmp: Node = self.head\n            count: int = 0\n            while count < index:\n                tmp = tmp.next\n                count += 1\n            tmp.prev.next = tmp.next\n            tmp.next.prev = tmp.prev\n\n    def insert_after(self, list_element: int, new_element: int):\n        if self.is_empty():\n            return\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            if tmp.key == list_element:\n                if tmp == self.tail:\n                    self.add_to_tail(new_element)\n                else:\n                    new_node = Node(new_element, tmp, tmp.next)\n                    tmp.next = new_node\n                    new_node.next.prev = new_node\n                tmp = tmp.next\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n\n    def insert_before(self, list_element: int, new_element: int):\n        if self.is_empty():\n            return\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            if tmp.key == list_element:\n                if tmp == self.head:\n                    self.add_to_head(new_element)\n                else:\n                    new_node = Node(new_element, tmp.prev, tmp)\n                    new_node.prev.next = new_node\n                    tmp.prev = new_node\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n\n    def sort(self):\n        swapped: bool = True\n        outer: Node = self.head\n        inner: Node = self.tail\n        while outer != self.tail:\n            inner = self.tail\n            while inner != outer:\n                if inner.key < inner.prev.key:\n                    k = inner.key\n                    inner.key = inner.prev.key\n                    inner.prev.key = k\n                inner = inner.prev\n            outer = outer.next\n\n\n"
  },
  {
    "path": "linked-lists/circular-doubly-linked-list/node.py",
    "content": "class Node:\n    def __init__(self, key: int=None, prev=None, next=None):\n        self.key = key\n        self.prev = prev\n        self.next = next\n\n"
  },
  {
    "path": "linked-lists/circular-singly-linked-list/list.py",
    "content": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = None\n\n    def is_empty(self) -> bool:\n        return self.head is None\n\n    def print_all(self):\n        if self.is_empty():\n            return\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            print(tmp.key, end=\", \")\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n        print(\"\")\n\n    def number_of_elements(self) -> int:\n        if self.is_empty():\n            return 0\n        count: int = 0\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            count += 1\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n        return count\n\n    def add_to_head(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n            self.tail.next = self.head\n        else:\n            self.head = Node(key, self.head)\n            self.tail.next = self.head\n\n    def add_to_tail(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n            self.tail.next = self.head\n        else:\n            self.tail.next = Node(key, self.head)\n            self.tail = self.tail.next\n\n    def delete_from_head(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.head\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                self.head = self.head.next\n                self.tail.next = self.head\n            return ret_node.key\n\n    def delete_from_tail(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.tail\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                tmp: Node = self.head\n                while tmp.next != self.tail:\n                    tmp = tmp.next\n                self.tail = tmp\n                tmp.next = self.head\n            return ret_node.key\n\n    def delete_nodes_with_value(self, key: int):\n        if self.is_empty():\n            return None\n        ret_value: int = None\n        tmp: Node = self.head\n        while tmp.next != self.head:\n            if tmp.next.key == key:\n                ret_value = tmp.next.key\n                tmp.next = tmp.next.next\n            else:\n                tmp = tmp.next\n        self.tail = tmp\n        if self.head.key == key:\n            ret_value = self.delete_from_head()\n        return ret_value\n\n    def delete_on_index(self, index: int):\n        if self.is_empty():\n            return\n        end_index: int = self.number_of_elements() - 1\n        if index < 0 or index > end_index:\n            return\n        if index == 0:\n            self.delete_from_head()\n        elif index == end_index:\n            self.delete_from_tail()\n        else:\n            prev: Node = None\n            tmp: Node = self.head\n            count: int = 0\n            while count < index:\n                prev = tmp\n                tmp = tmp.next\n                count += 1\n            prev.next = tmp.next\n\n    def insert_after(self, list_element: int, new_element: int):\n        if self.is_empty():\n            return\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            if tmp.key == list_element:\n                if tmp == self.tail:\n                    self.add_to_tail(new_element)\n                else:\n                    tmp.next = Node(new_element, tmp.next)\n                tmp = tmp.next\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n\n    def insert_before(self, list_element: int, new_element: int):\n        if self.is_empty():\n            return\n        prev: Node = None\n        tmp: Node = self.head\n        stopped: bool = False\n        while not stopped:\n            if tmp.key == list_element:\n                if tmp == self.head:\n                    self.add_to_head(new_element)\n                else:\n                    prev.next = Node(new_element, tmp)\n            prev = tmp\n            tmp = tmp.next\n            if tmp == self.head:\n                stopped = True\n\n    def sort(self):\n        swapped: bool = True\n        while swapped:\n            swapped = False\n            tmp: Node = self.head\n            while tmp != self.tail:\n                if tmp.key > tmp.next.key:\n                    k = tmp.key\n                    tmp.key = tmp.next.key\n                    tmp.next.key = k\n                    swapped = True\n                tmp = tmp.next\n\n\n"
  },
  {
    "path": "linked-lists/circular-singly-linked-list/node.py",
    "content": "class Node:\n    def __init__(self, key: int=None, next=None):\n        self.key = key\n        self.next = next\n\n"
  },
  {
    "path": "linked-lists/doubly-linked-list/list.py",
    "content": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = None\n\n    def is_empty(self) -> bool:\n        return self.head is None\n\n    def print_all(self):\n        tmp: Node = self.head\n        while tmp is not None:\n            print(tmp.key, end=\", \")\n            tmp = tmp.next\n        print(\"\")\n\n    def number_of_elements(self) -> int:\n        count: int = 0\n        tmp: Node = self.head\n        while tmp is not None:\n            count += 1\n            tmp = tmp.next\n        return count\n\n    def add_to_head(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n        else:\n            self.head = Node(key, None, self.head)\n            self.head.next.prev = self.head\n\n    def add_to_tail(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n        else:\n            self.tail.next = Node(key, self.tail)\n            self.tail = self.tail.next\n\n    def delete_from_head(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.head\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                self.head = self.head.next\n                self.head.prev = None\n            return ret_node.key\n\n    def delete_from_tail(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.tail\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                self.tail = self.tail.prev;\n                self.tail.next = None\n            return ret_node.key\n\n    def delete_nodes_with_value(self, key: int):\n        if self.is_empty():\n            return None\n        ret_value: int = None\n        tmp: Node = self.head\n        while tmp.next is not None:\n            if tmp.next.key == key:\n                ret_value = tmp.next.key\n                tmp.next = tmp.next.next\n                if tmp.next is not None:\n                    tmp.next.prev = tmp\n            else:\n                tmp = tmp.next\n        self.tail = tmp\n        if self.head.key == key:\n            ret_value = self.delete_from_head()\n        return ret_value\n\n    def delete_on_index(self, index: int):\n        if self.is_empty():\n            return\n        end_index: int = self.number_of_elements() - 1\n        if index < 0 or index > end_index:\n            return\n        if index == 0:\n            self.delete_from_head()\n        elif index == end_index:\n            self.delete_from_tail()\n        else:\n            tmp: Node = self.head\n            count: int = 0\n            while count < index:\n                tmp = tmp.next\n                count += 1\n            tmp.prev.next = tmp.next\n            tmp.next.prev = tmp.prev\n\n    def insert_after(self, list_element: int, new_element: int):\n        tmp: Node = self.head\n        while tmp is not None:\n            if tmp.key == list_element:\n                if tmp == self.tail:\n                    self.add_to_tail(new_element)\n                else:\n                    new_node: Node = Node(new_element, tmp, tmp.next)\n                    tmp.next = new_node\n                    new_node.next.prev = new_node\n                tmp = tmp.next\n            tmp = tmp.next\n\n    def insert_before(self, list_element: int, new_element: int):\n        tmp: Node = self.head\n        while tmp is not None:\n            if tmp.key == list_element:\n                if tmp == self.head:\n                    self.add_to_head(new_element)\n                else:\n                    new_node: Node = Node(new_element, tmp.prev, tmp)\n                    new_node.prev.next = new_node\n                    tmp.prev = new_node\n            tmp = tmp.next\n\n    def sort(self):\n        swapped: bool = True\n        outer: Node = self.head\n        inner: Node = self.tail\n        while outer != self.tail:\n            inner = self.tail\n            while inner != outer:\n                if inner.key < inner.prev.key:\n                    k = inner.key\n                    inner.key = inner.prev.key\n                    inner.prev.key = k\n                inner = inner.prev\n            outer = outer.next\n\n\n"
  },
  {
    "path": "linked-lists/doubly-linked-list/node.py",
    "content": "class Node:\n    def __init__(self, key: int=None, prev=None, next=None):\n        self.key = key\n        self.prev = prev\n        self.next = next\n\n"
  },
  {
    "path": "linked-lists/singly-linked-list/list.py",
    "content": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = None\n\n    def is_empty(self) -> bool:\n        return self.head is None\n\n    def print_all(self):\n        tmp: Node = self.head\n        while tmp is not None:\n            print(tmp.key, end=\", \")\n            tmp = tmp.next\n        print(\"\")\n\n    def number_of_elements(self) -> int:\n        count: int = 0\n        tmp: Node = self.head\n        while tmp is not None:\n            count += 1\n            tmp = tmp.next\n        return count\n\n    def add_to_head(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n        else:\n            self.head = Node(key, self.head)\n\n    def add_to_tail(self, key: int):\n        if self.is_empty():\n            self.head = self.tail = Node(key)\n        else:\n            self.tail.next = Node(key)\n            self.tail = self.tail.next\n\n    def delete_from_head(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.head\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                self.head = self.head.next\n            return ret_node.key\n\n    def delete_from_tail(self) -> int:\n        if self.is_empty():\n            return None\n        else:\n            ret_node: Node = self.tail\n            if self.head == self.tail:\n                self.head = self.tail = None\n            else:\n                tmp: Node = self.head\n                while tmp.next != self.tail:\n                    tmp = tmp.next\n                tmp.next = None\n                self.tail = tmp\n            return ret_node.key\n\n    def delete_nodes_with_value(self, key: int):\n        if self.is_empty():\n            return None\n        ret_value: int = None\n        tmp: Node = self.head\n        while tmp.next is not None:\n            if tmp.next.key == key:\n                ret_value = tmp.next.key\n                tmp.next = tmp.next.next\n            else:\n                tmp = tmp.next\n        self.tail = tmp\n        if self.head.key == key:\n            ret_value = self.delete_from_head()\n        return ret_value\n\n    def delete_on_index(self, index: int):\n        if self.is_empty():\n            return\n        end_index: int = self.number_of_elements() - 1\n        if index < 0 or index > end_index:\n            return\n        if index == 0:\n            self.delete_from_head()\n        elif index == end_index:\n            self.delete_from_tail()\n        else:\n            prev: Node = None\n            tmp: Node = self.head\n            count: int = 0\n            while count < index:\n                prev = tmp\n                tmp = tmp.next\n                count += 1\n            prev.next = tmp.next\n\n    def insert_after(self, list_element: int, new_element: int):\n        tmp: Node = self.head\n        while tmp is not None:\n            if tmp.key == list_element:\n                if tmp == self.tail:\n                    self.add_to_tail(new_element)\n                else:\n                    tmp.next = Node(new_element, tmp.next)\n                tmp = tmp.next\n            tmp = tmp.next\n\n    def insert_before(self, list_element: int, new_element: int):\n        prev: Node = None\n        tmp: Node = self.head\n        while tmp is not None:\n            if tmp.key == list_element:\n                if tmp == self.head:\n                    self.add_to_head(new_element)\n                else:\n                    prev.next = Node(new_element, tmp)\n            prev = tmp\n            tmp = tmp.next\n\n    def sort(self):\n        swapped: bool = True\n        while swapped:\n            swapped = False\n            tmp: Node = self.head\n            while tmp != self.tail:\n                if tmp.key > tmp.next.key:\n                    k = tmp.key\n                    tmp.key = tmp.next.key\n                    tmp.next.key = k\n                    swapped = True\n                tmp = tmp.next\n\n"
  },
  {
    "path": "linked-lists/singly-linked-list/node.py",
    "content": "class Node:\n    def __init__(self, key: int=None, next=None):\n        self.key = key\n        self.next = next\n\n"
  },
  {
    "path": "queue/circular-queue-fixed-size-array-impl.py",
    "content": "class Queue:\n    def __init__(self, length: int = 10):\n        self._length: int = length\n        self._queue: list = [None] * self._length\n        self._front = self._rear = -1\n        self._count: int = 0\n\n    def enqueue(self, item):\n        if self.is_full():\n            raise Exception(\"Queue is full\")\n        self._rear = (self._rear + 1) % self._length\n        if self._front == -1:\n            self._front += 1\n        self._count += 1\n        self._queue[self._rear] = item\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        ret_value = self._queue[self._front]\n        if self._front == self._rear:\n            self._front = self._rear = -1\n        else:\n            self._front = (self._front + 1) % self._length\n        self._count -= 1\n        return ret_value\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        return self._queue[self._front]\n\n    def size(self) -> int:\n        return self._count\n\n    def is_empty(self) -> bool:\n        return self._front == -1\n\n    def is_full(self) -> bool:\n        return (self._rear + 1) % self._length == self._front\n\n\ndef hot_potato(namelist, number):\n    q = Queue(30)\n\n    for name in namelist:\n        q.enqueue(name)\n\n    while q.size() > 1:\n        for i in range(number):\n            q.enqueue(q.dequeue())\n        q.dequeue()\n\n    return q.dequeue()\n\n\nprint(hot_potato([\"Bill\", \"David\", \"Susan\", \"Jane\", \"Kent\", \"Brad\"], 7))\n\n"
  },
  {
    "path": "queue/queue-array-impl.py",
    "content": "class Queue:\n    def __init__(self):\n        self._queue = []\n\n    def enqueue(self, item):\n        self._queue.insert(0, item)\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception('Queue is empty')\n        return self._queue.pop()\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception('Queue is empty')\n        return self._queue[len(self._queue) - 1]\n\n    def size(self) -> int:\n        return len(self._queue)\n\n    def is_empty(self) -> bool:\n        return len(self._queue) == 0\n\n\ndef hot_potato(namelist, number):\n    q = Queue()\n\n    for name in namelist:\n        q.enqueue(name)\n\n    while q.size() > 1:\n        for i in range(number):\n            q.enqueue(q.dequeue())\n        q.dequeue()\n\n    return q.dequeue()\n\n\nprint(hot_potato([\"Bill\", \"David\", \"Susan\", \"Jane\", \"Kent\", \"Brad\"], 7))\n"
  },
  {
    "path": "queue/queue-fixed-size-array-impl.py",
    "content": "class Queue:\n    def __init__(self, length: int = 10):\n        self._length: int = length\n        self._queue: list = [None] * self._length\n        self._front = self._rear = -1\n        self._count: int = 0\n\n    def enqueue(self, item):\n        if self.is_full():\n            raise Exception(\"Queue is full\")\n        self._rear += 1\n        if self._front == -1:\n            self._front += 1\n        self._count += 1\n        self._queue[self._rear] = item\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        ret_value = self._queue[self._front]\n        if self._front == self._rear:\n            self._front = self._rear = -1\n        else:\n            self._front += 1\n        self._count -= 1\n        return ret_value\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        return self._queue[self._front]\n\n    def size(self) -> int:\n        return self._count\n\n    def is_empty(self) -> bool:\n        return self._front == -1\n\n    def is_full(self) -> bool:\n        return self._rear == self._length - 1\n\n\ndef hot_potato(namelist, number):\n    q = Queue(300)\n\n    for name in namelist:\n        q.enqueue(name)\n\n    while q.size() > 1:\n        for i in range(number):\n            q.enqueue(q.dequeue())\n        q.dequeue()\n\n    return q.dequeue()\n\n\nprint(hot_potato([\"Bill\", \"David\", \"Susan\", \"Jane\", \"Kent\", \"Brad\"], 7))\n\n"
  },
  {
    "path": "queue/queue-linked-list-impl.py",
    "content": "\nclass Node:\n    def __init__(self, key, next=None):\n        self.key = key\n        self.next = next\n\n\nclass Queue:\n    def __init__(self):\n        self._head = self._tail = None\n        self._count: int = 0\n\n    def enqueue(self, item):\n        if self.is_empty():\n            self._head = self._tail = Node(item)\n        else:\n            self._tail.next = Node(item)\n            self._tail = self._tail.next\n        self._count += 1\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        ret_value: Node = self._head\n        if self._head == self._tail:\n            self._head = self._tail = None\n        else:\n            self._head = self._head.next\n        self._count -= 1\n        return ret_value.key\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        return self._head.key\n\n    def size(self) -> int:\n        return self._count\n\n    def is_empty(self) -> bool:\n        return self._head is None\n\n\ndef hot_potato(namelist, number):\n    q: Queue = Queue()\n    for person in namelist:\n        q.enqueue(person)\n    while q.size() > 1:\n        for i in range(number):\n            q.enqueue(q.dequeue())\n        q.dequeue()\n    return q.dequeue()\n\n\nprint(hot_potato([\"Bill\", \"David\", \"Susan\", \"Jane\", \"Kent\", \"Brad\"], 7))\n\n"
  },
  {
    "path": "queue/queue-two-stacks-impl.py",
    "content": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n    def push(self, item):\n        self.items.append(item)\n\n    def pop(self):\n        return self.items.pop()\n\n    def peek(self):\n        return self.items[len(self.items) - 1]\n\n    def size(self):\n        return len(self.items)\n\n\nclass Queue:\n    def __init__(self):\n        self.input_stack: Stack = Stack()\n        self.output_stack: Stack = Stack()\n\n    def is_empty(self) -> bool:\n        return self.input_stack.is_empty()\n\n    def size(self) -> int:\n        return self.input_stack.size()\n\n    def enqueue(self, item):\n        self.input_stack.push(item)\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        while not self.input_stack.is_empty():\n            self.output_stack.push(self.input_stack.pop())\n        ret_value = self.output_stack.pop()\n        while not self.output_stack.is_empty():\n            self.input_stack.push(self.output_stack.pop())\n        return ret_value\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        while not self.input_stack.is_empty():\n            self.output_stack.push(self.input_stack.pop())\n        ret_value = self.input_stack.pop()\n        self.output_stack.push(ret_value)\n        while not self.output_stack.is_empty():\n            self.input_stack.push(self.output_stack.pop())\n        return ret_value\n\n\ndef hot_potato(people: list, num: int) -> str:\n    q: Queue = Queue()\n    for person in people:\n        q.enqueue(person)\n\n    while q.size() > 1:\n        for i in range(num):\n            q.enqueue(q.dequeue())\n        q.dequeue()\n\n    return q.dequeue()\n\n\nprint(hot_potato([\"Bill\", \"David\", \"Susan\", \"Jane\", \"Kent\", \"Brad\"], 7))\n"
  },
  {
    "path": "recursion/convert-number-iterative.py",
    "content": "from stack import Stack\n\n\ndef converter(num: int, base: int) -> str:\n    digits = \"0123456789ABCDEF\"\n    stack: Stack = Stack()\n    while num > 0:\n        stack.push(digits[num % base])\n        num = num // base\n    result: str = \"\"\n    while not stack.is_empty():\n        result += stack.pop()\n    return result\n\n\n'''\ndigits = \"0123456789ABCDEF\"\ndef converter(num: int, base: int) -> str:\n\n    r:str = ''\n    while num >  0:\n        r = digits[num % base] + r\n        num = num // base       \n    \n    return r\n'''\n\nprint(converter(1453, 16)) # 5AD\n"
  },
  {
    "path": "recursion/convert-number.py",
    "content": "def converter(num: int, base: int) -> str:\n    digits = \"0123456789ABCDEF\"\n    if num < base:\n        return digits[num]\n    else:\n        return converter(num // base, base) + digits[num % base]\n\n\nprint(converter(1453, 16)) # 5AD"
  },
  {
    "path": "recursion/factorial.py",
    "content": "def factorial_rec(n: int) -> int:\n    if n <= 1:\n        return 1\n    else:\n        return n * factorial_rec(n - 1)\n\n\ndef factorial_it(n: int) -> int:\n    if n <= 1:\n        return 1\n    result: int = 1\n    while n > 1:\n        result *= n\n        n -= 1\n    return result\n"
  },
  {
    "path": "recursion/fibonacci-iterative.py",
    "content": "def fibonacci(n: int) -> int:\n    if n <= 1:\n        return 0\n    elif n == 2:\n        return 1\n    prev: int = 0\n    curr: int = 1\n    for _ in range(n - 2):\n        prev, curr = curr, prev + curr\n    return curr\n\n\nfor i in range(6):\n    print(fibonacci(i))\n"
  },
  {
    "path": "recursion/fibonacci-memoization.py",
    "content": "\nnums: dict = {}\n\n\ndef fibonacci(n: int) -> int:\n    if n <= 1:\n        return 0\n    elif n == 2:\n        return 1\n    else:\n        if n in nums:\n            return nums[n]\n        nums[n] = fibonacci(n - 1) + fibonacci(n - 2)\n        return nums[n]\n\n\nfor i in range(6):\n    print(fibonacci(i))\n"
  },
  {
    "path": "recursion/fibonacci-recursive-worst-solution.py",
    "content": "def fibonacci(n: int) -> int:\n    if n <= 1:\n        return 0\n    elif n == 2:\n        return 1\n    else:\n        return fibonacci(n - 1) + fibonacci(n - 2)\n\n\nfor i in range(6):\n    print(fibonacci(i))\n"
  },
  {
    "path": "recursion/fibonacci-recursive.py",
    "content": "def fibonacci(n: int) -> int:\n\n    def fibonacci_helper(num: int, prev: int = 0, curr: int = 1) -> int:\n        if num <= 1:\n            return prev\n        elif num == 2:\n            return curr\n        else:\n            return fibonacci_helper(num - 1, curr, prev + curr)\n\n    return fibonacci_helper(n)\n\n\nfor i in range(6):\n    print(fibonacci(i))\n"
  },
  {
    "path": "recursion/fibonacci-sum-iterative.py",
    "content": "def fibonaci(n: int):\n    \n    if n == 1:\n        return 0\n    if n == 2:\n        return 1\n\n    prev: int = 0\n    curr: int = 1\n    acc: int = 1\n\n    for _ in range(n - 2):\n        prev,curr = curr, prev + curr\n        acc += curr\n\n\n    return acc\n\n\nfor i in range(6):\n    print(f'Fibonacci {i} is : {fibonacci(i)}')\n"
  },
  {
    "path": "recursion/fibonacci-sum-recursive.py",
    "content": "def fibonacci(n: int) -> int:\n\n    def fibonacci_helper(num: int, acc: int = 0, prev: int = 0, curr: int = 1):\n        if num <= 1:\n            return prev\n        elif num == 2:\n            return acc + curr\n        else:\n            return fibonacci_helper(num - 1, acc + curr, curr, prev + curr)\n\n    return fibonacci_helper(n)\n\n\nfor i in range(6):\n    print(fibonacci(i))\n"
  },
  {
    "path": "recursion/maze.py",
    "content": "\nfrom typing import List, Any\n\nclass Stack:\n\n    def __init__(self) -> None:\n        self.stack:List[Any] = list()\n\n\n    def is_empty(self) -> bool:\n        return len(self.stack) == 0\n    \n\n    def size(self) -> int:\n        return len(self.stack)\n    \n\n    def push(self, item:Any) -> None:\n        self.stack.append(item)\n    \n\n    def peek(self) -> Any:\n        if self.is_empty():\n            raise Exception('Stack is empty')\n        return self.stack[len(self.stack) - 1]\n    \n\n    def pop(self) -> Any:\n        if self.is_empty():\n            raise Exception('Stack is empty')\n        return self.stack.pop()\n\nsize: int = 5\nmatrix =  [[0 for x in range(size)] for y in range(size)]\n\n'''\n0 - not visited\n1 - visited\n2 - obstacle\n'''\n# place maze obstacles\nmatrix[0][2] = 2\nmatrix[0][3] = 2\nmatrix[1][0] = 2\nmatrix[2][2] = 2\nmatrix[2][3] = 2\nmatrix[2][4] = 2\nmatrix[3][1] = 2\nmatrix[4][3] = 2\n\n# starting position\nmatrix[0][0] = 1\n\nstack = Stack()\nstack.push([0, 0])\n\ndef path_finder(matrix:List[List[int]], stack:Stack) -> None:\n    _move(matrix, stack)\n\n\ndef _move(matrix:List[List[int]], stack:Stack) -> None:\n\n    if stack.is_empty():\n        print('Path not found')\n    else:\n        x, y = stack.peek()\n        if x == len(matrix) - 1 and y == len(matrix[x]) - 1:\n            print('Path found')\n        elif y < len(matrix[x]) - 1 and matrix[x][y + 1] == 0:\n            _add_coordinates_to_stack(matrix, stack, x, y + 1)\n            _move(matrix, stack)\n        elif y > 0 and matrix[x][y - 1] == 0:\n            _add_coordinates_to_stack(matrix, stack, x, y - 1)\n            _move(matrix, stack)\n        elif x > 0 and matrix[x - 1][y] == 0:\n            _add_coordinates_to_stack(matrix, stack, x - 1, y)\n            _move(matrix, stack)\n        elif x < len(matrix) - 1 and matrix[x + 1][y] == 0: \n            _add_coordinates_to_stack(matrix, stack, x + 1, y)\n            _move(matrix, stack)\n        else:\n            stack.pop()\n            _move(matrix, stack)\n\n\ndef _add_coordinates_to_stack(matrix:List[List[int]], stack:Stack, x:int, y:int) -> None:\n    matrix[x][y] = 1\n    stack.push([x, y])\n\n\nif __name__ == \"__main__\":\n\n    path_finder(matrix, stack)\n\n        \n\n\n"
  },
  {
    "path": "recursion/palindrome.py",
    "content": "def palindrome_checker(string: str) -> bool:\n\n    def palindrome_helper(s: str, start: int, end: int) -> bool:\n        if start >= end:\n            return True\n        else:\n            if s[start] == s[end]:\n                return palindrome_helper(s, start + 1, end - 1)\n            else:\n                return False\n\n    return palindrome_helper(string, 0, len(string) - 1)\n\n\nstr1: str = \"kayak\"\nstr2: str = \"aibohphobia\"\n\nprint(palindrome_checker(str1))\nprint(palindrome_checker(str2))\n\n\ndef palindrome_checker_iterative(string:str) -> bool:\n    is_palindrome: bool = True\n    start = 0\n    end = len(string) - 1\n\n    while start < end and is_palindrome:\n        if string[start] != string[end]:\n            is_palindrome = False\n        start += 1\n        end -= 1\n    \n    return is_palindrome\n\n\nprint(palindrome_checker_iterative(str1))\nprint(palindrome_checker_iterative(str2))\n\n\ndef palindrome_checker_slicing(string: str) -> bool:\n    if len(string) <= 1:\n        return True\n    else:\n        if string[0] == string[-1]:\n            return palindrome_checker_slicing(string[1: -1])\n        else:\n            return False\n\n\nprint(palindrome_checker_slicing(str1))\nprint(palindrome_checker_slicing(str2))\n"
  },
  {
    "path": "recursion/reverse-linked-list-iterative-stack.py",
    "content": "from stack import Stack\n\nclass ListNode:\n    def __init__(self, payload = None, next: 'ListNode' = None) -> None:\n        self.payload = payload\n        self.next: 'ListNode' = next\n\n\n\ndef reverse_list(head: ListNode) -> ListNode:\n    if head is None or head.next is None:\n        return head\n\n    stack: Stack = Stack()\n\n    while head is not None:\n        stack.push(head)\n        head = head.next\n\n    dummy: ListNode = ListNode()\n    dummy_tmp = dummy\n\n    while not stack.is_empty():\n        dummy_tmp.next = stack.pop()\n        dummy_tmp = dummy_tmp.next\n\n    dummy_tmp.next = None\n    return dummy.next\n\n\n    \n"
  },
  {
    "path": "recursion/reverse-linked-list-iterative.py",
    "content": "class ListNode:\n    def __init__(self, val=0, next=None):\n        self.val = val\n        self.next = next\n\n\ndef reverse_list(head: ListNode) -> ListNode:\n    if head is None or head.next is None:\n        return head\n\n    stopped: bool = False\n    prev = head.next\n    head.next = None\n    while not stopped:\n        tmp: ListNode = prev.next\n        prev.next = head\n        head = prev\n        if tmp is not None:\n            prev = tmp\n        else:\n            stopped = True\n\n    return prev\n"
  },
  {
    "path": "recursion/reverse-linked-list.py",
    "content": "class ListNode:\n    def __init__(self, val: int = None, next = None):\n        self.val = val\n        self.next = next\n\n\ndef reverse_linked_list(node: ListNode) -> ListNode:\n    if node is None:\n        return None\n    elif node.next is None:\n        return node\n    else:\n        next_node: ListNode = node.next\n        node.next = None\n        rest: ListNode = reverse_linked_list(next_node)\n        next_node.next = node\n        return rest\n\n"
  },
  {
    "path": "recursion/reverse-list.py",
    "content": "nums: list = [1, 2, 3, 4, 5]\n\n\ndef reverse_rec(elements: list):\n\n    def reverse_list_helper(values: list, start: int, end: int):\n        if start < end:\n            values[start], values[end] = values[end], values[start]\n            reverse_list_helper(values, start + 1, end - 1)\n\n    reverse_list_helper(elements, 0, len(elements) - 1)\n\n\nprint(nums)\nreverse_rec(nums)\nprint(nums)\n\n\ndef reverse_iterative(elements: list):\n    start: int = 0\n    end: int = len(elements) - 1\n    while start < end:\n        elements[start], elements[end] = elements[end], elements[start]\n        start += 1\n        end -= 1\n\n\nreverse_iterative(nums)\nprint(nums)\n"
  },
  {
    "path": "recursion/reverse-string.py",
    "content": "\nstring: str = \"This string will be reversed\"\n\n\ndef reverse_string(string: str) -> str:\n\n    def helper(s: str, end: int) -> str:\n        if end < 0:\n            return \"\"\n        else:\n            return s[end] + helper(s, end - 1)\n\n    return helper(string, len(string) - 1)\n\n\nprint(reverse_string(string))\n\n\ndef reverse_string_ex_one(string: str) -> str:\n    if len(string) == 0:\n        return \"\"\n    else:\n        return reverse_string_ex_one(string[1:]) + string[0]\n\n\nprint(reverse_string_ex_one(string))\n\n\ndef reverse_string_ex_two(string: str) -> str:\n    if len(string) == 0:\n        return \"\"\n    else:\n        return string[len(string) - 1] + reverse_string_ex_two(string[0: len(string) - 1])\n\n\nprint(reverse_string_ex_two(string))\n"
  },
  {
    "path": "recursion/stack.py",
    "content": "class Stack:\n\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n    def push(self, item):\n        self.items.append(item)\n\n    def pop(self):\n        return self.items.pop()\n\n    def peek(self):\n        return self.items[len(self.items) - 1]\n\n    def size(self):\n        return len(self.items)"
  },
  {
    "path": "recursion/sum-numbers-binary-recursion.py",
    "content": "numbers: list = [1, 2, 3, 4, 5]\n\n\n# O(n) time\ndef sum_numbers(nums: list) -> int:\n\n    def helper(elements: list, start: int, end: int) -> int:\n        if start > end:\n            return 0\n        else:\n            index: int = start + (end - start) // 2\n            return helper(elements, start, index - 1) + elements[index] + helper(elements, index + 1, end)\n\n    return helper(nums, 0, len(nums) - 1)\n\n\nprint(sum_numbers(numbers))\n"
  },
  {
    "path": "recursion/sum-numbers-pointer.py",
    "content": "numbers: list = [1, 2, 3, 4, 5]\n\n\n# O(n) time\ndef sum_numbers(nums: list) -> int:\n\n    def helper(elements: list, end: int) -> int:\n        if end < 0:\n            return 0\n        else:\n            return elements[end] + helper(elements, end - 1)\n\n    return helper(nums, len(nums) - 1)\n\n\nprint(sum_numbers(numbers))\n"
  },
  {
    "path": "recursion/sum-numbers-slicing.py",
    "content": "numbers: list = [1, 2, 3, 4, 5]\n\n\n# slicing is O(k), better is solution with pointer\ndef sum_numbers(nums: list) -> int:\n    if len(nums) == 0:\n        return 0\n    else:\n        return nums[0] + sum_numbers(nums[1:])\n\n\nprint(sum_numbers(numbers))\n"
  },
  {
    "path": "recursion/towers-of-hanoi.py",
    "content": "def print_move(start: str, end: str):\n    print(\"Moving from\", start, \"to\", end)\n\n\ndef towers(number: int, start: str, spare: str, end: str):\n    if number == 1:\n        print_move(start, end)\n    else:\n        towers(number - 1, start, end, spare)\n        towers(1, start, spare, end)\n        towers(number - 1, spare, start, end)\n\n\ntowers(3, \"start\", \"spare\", \"end\")\n"
  },
  {
    "path": "searching/binary-search-iterative.py",
    "content": "\ndef binary_search(nums: list, target: int) -> bool:\n    start: int = 0\n    end: int = len(nums) - 1\n    found: bool = False\n    while start <= end and not found:\n        midpoint: int = start + (end - start) // 2\n        if nums[midpoint] == target:\n            found = True\n        else:\n            if nums[midpoint] > target:\n                end = midpoint - 1\n            else:\n                start = midpoint + 1\n    return found\n\n\ntestlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]\nprint(binary_search(testlist, 3))\nprint(binary_search(testlist, 13))\n"
  },
  {
    "path": "searching/binary-search-recursive-pointers.py",
    "content": "\ndef binary_search(nums: list, target: int) -> bool:\n\n    def binary_search_helper(numbers: list, element, start: int, end: int) -> bool:\n        if start > end:\n            return False\n        else:\n            midpoint: int = start + (end - start) // 2\n            if nums[midpoint] == element:\n                return True\n            else:\n                if nums[midpoint] > element:\n                    return binary_search_helper(numbers, element, start, midpoint - 1)\n                else:\n                    return binary_search_helper(numbers, element, midpoint + 1, end)\n\n    return binary_search_helper(nums, target, 0, len(nums) - 1)\n\n\ntestlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]\nprint(binary_search(testlist, 3))\nprint(binary_search(testlist, 13))\n"
  },
  {
    "path": "searching/binary-search-recursive.py",
    "content": "# slicing a list is O(k)\n# better is recursive solution with pointers\ndef binary_search(nums: list, target: int) -> bool:\n    if len(nums) == 0:\n        return False\n    else:\n        midpoint: int = len(nums) // 2\n        if nums[midpoint] == target:\n            return True\n        else:\n            if nums[midpoint] > target:\n                return binary_search(nums[0: midpoint], target)\n            else:\n                return binary_search(nums[midpoint + 1:], target)\n\n\ntestlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]\nprint(binary_search(testlist, 3))\nprint(binary_search(testlist, 13))\n"
  },
  {
    "path": "searching/sequential-search-ordered-list.py",
    "content": "\ndef sequential_search(nums: list, target: int) -> bool:\n    found: bool = False\n    stopped: bool = False\n    i: int = 0\n    while i < len(nums) and not found and not stopped:\n        if nums[i] == target:\n            found = True\n        else:\n            if nums[i] > target:\n                stopped = True\n            else:\n                i += 1\n    return found\n\n\ntestlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]\nprint(sequential_search(testlist, 3))\nprint(sequential_search(testlist, 13))\n"
  },
  {
    "path": "searching/sequential-search-unordered-list.py",
    "content": "\ndef sequential_search(nums: list, target: int) -> bool:\n    i: int = 0\n    found: bool = False\n    while i < len(nums) and not found:\n        if nums[i] == target:\n            found = True\n        else:\n            i += 1\n    return found\n\n\ntestlist = [1, 2, 32, 8, 17, 19, 42, 13, 0]\nprint(sequential_search(testlist, 3))\nprint(sequential_search(testlist, 13))\n"
  },
  {
    "path": "sorting/bubble-sort.py",
    "content": "\narray = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef bubble_sort(nums: list):\n    for i in range(len(nums)):\n        for j in range(len(nums) - 1, i, -1):\n            if nums[j - 1] > nums[j]:\n                nums[j - 1], nums[j] = nums[j], nums[j - 1]\n\n\nprint(array)\nbubble_sort(array)\nprint(array)\n"
  },
  {
    "path": "sorting/insertion-sort.py",
    "content": "\narray = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef insertion_sort(nums: list):\n    for i in range(1, len(nums), +1):\n        curr: int = nums[i]\n        pos: int = i\n        while pos > 0 and nums[pos - 1] > curr:\n            nums[pos] = nums[pos - 1]\n            pos -= 1\n        nums[pos] = curr\n\n\nprint(array)\ninsertion_sort(array)\nprint(array)\n"
  },
  {
    "path": "sorting/merge-sort.py",
    "content": "\narray = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef merge_sort(nums: list):\n    if len(nums) < 2:\n        return\n    midpoint: int = len(nums) // 2\n    left: list = nums[0:midpoint]\n    right: list = nums[midpoint:]\n    merge_sort(left)\n    merge_sort(right)\n    i = j = k = 0\n    while i < len(left) and j < len(right):\n        if left[i] < right[j]:\n            nums[k] = left[i]\n            i += 1\n        else:\n            nums[k] = right[j]\n            j += 1\n        k += 1\n    while i < len(left):\n        nums[k] = left[i]\n        i += 1\n        k += 1\n    while j < len(right):\n        nums[k] = right[j]\n        j += 1\n        k += 1\n\n\nprint(array)\nmerge_sort(array)\nprint(array)"
  },
  {
    "path": "sorting/quicksort-return-new-array.py",
    "content": "array = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef quick_sort(nums: list) -> list:\n    if len(nums) < 2:\n        return nums\n    pivot_index: int = len(nums) // 2\n    pivot_value: int = nums[pivot_index]\n\n    left: list = []\n    right: list = []\n    for i in range(len(nums)):\n        if i != pivot_index:\n            if nums[i] < pivot_value:\n                left.append(nums[i])\n            else:\n                right.append(nums[i])\n    return quick_sort(left) + [pivot_value] + quick_sort(right)\n\n\nprint(array)\narray_sorted: list = quick_sort(array)\nprint(array_sorted)\n"
  },
  {
    "path": "sorting/quicksort.py",
    "content": "array = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef quick_sort(nums: list, i: int, j: int):\n    if i < j:\n        left: int = i\n        right: int = j\n        pointer: int = left\n        pivot_index: int = left + (right - left) // 2\n        pivot_value: int = nums[pivot_index]\n\n        while pointer <= right:\n            if nums[pointer] < pivot_value:\n                nums[left], nums[pointer] = nums[pointer], nums[left]\n                left += 1\n                pointer += 1\n            elif nums[pointer] > pivot_value:\n                nums[pointer], nums[right] = nums[right], nums[pointer]\n                right -= 1\n            else:\n                pointer += 1\n\n        quick_sort(nums, i, left)\n        if pointer > left:\n            quick_sort(nums, pointer, j)\n        else:\n            quick_sort(nums, pointer + 1, j)\n\n\nprint(array)\nquick_sort(array, 0, len(array) - 1)\nprint(array)\n"
  },
  {
    "path": "sorting/selection-sort.py",
    "content": "\narray = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef selection_sort(nums: list):\n    for i in range(0, len(nums), +1):\n        min_index: int = i\n        for j in range(i + 1, len(nums) - 1, +1):\n            if nums[j] < nums[min_index]:\n                min_index = j\n        if min_index != i:\n            nums[min_index], nums[i] = nums[i], nums[min_index]\n\n\nprint(array)\nselection_sort(array)\nprint(array)\n"
  },
  {
    "path": "sorting/short-bubble.py",
    "content": "\narray = [54, 26, 93, 17, 77, 31, 44, 55, 20]\n\n\ndef short_bubble(nums: list):\n    swapped: bool = True\n    dec: int = 1\n    while swapped:\n        swapped = False\n        for i in range(len(nums) - dec):\n            if nums[i] > nums[i + 1]:\n                nums[i + 1], nums[i] = nums[i], nums[i + 1]\n                swapped = True\n        dec += 1\n\n\nprint(array)\nshort_bubble(array)\nprint(array)\n"
  },
  {
    "path": "stack/examples/balanced-brackets.py",
    "content": "from stack import Stack\n\n\ndef balanced_brackets(string: str) -> bool:\n    stack: Stack = Stack()\n    for character in string:\n        if character in \"([{\":\n            stack.push(character)\n        if character in \")]}\":\n            if stack.is_empty():\n                return False\n            if \"([{\".index(stack.peek()) == \")]}\".index(character):\n                stack.pop()\n            else:\n                return False\n    \n    return stack.is_empty()\n\n\nprint(balanced_brackets('((()))'))  # True\nprint(balanced_brackets('(()'))  # False\nprint(balanced_brackets(']()'))  # False\nprint(balanced_brackets('(])'))  # False\n"
  },
  {
    "path": "stack/examples/number_converter.py",
    "content": "from stack import Stack\n\n\ndef base_converter(num, base) -> str:\n    digits = \"0123456789ABCDEF\"\n    stack: Stack = Stack()\n    while num > 0:\n        stack.push(digits[num % base])\n        num = num // base\n\n    result: str = \"\"\n    while not stack.is_empty():\n        result += stack.pop()\n\n    return result\n\n\nprint(base_converter(25, 8))  # 31\nprint(base_converter(256, 16))  # 100\nprint(base_converter(26, 26))  # 10\n"
  },
  {
    "path": "stack/examples/stack.py",
    "content": "class Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len(self._stack) == 0\n\n    def push(self, item):\n        self._stack.append(item)\n\n    def pop(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        # return self._stack[-1] # python way\n        return self._stack[len(self._stack) - 1]\n\n    def size(self) -> int:\n        return len(self._stack)\n"
  },
  {
    "path": "stack/stack-array-impl-less-efficient.py",
    "content": "# this solution is less efficient because\n# pop() from end of array is faster than pop(some_other_index)\n# append(item) is faster than insert(some_other_index, item)\nclass Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len(self._stack) == 0\n\n    def push(self, item):\n        self._stack.insert(0, item)\n\n    def pop(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        return self._stack.pop(0)\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        return self._stack[0]\n\n    def size(self) -> int:\n        return len(self._stack)\n\n\ndef reverse_string(s: str) -> str:\n    stack: Stack = Stack()\n    for character in s:\n        stack.push(character)\n\n    result: str = \"\"\n    while not stack.is_empty():\n        result += stack.pop()\n\n    return result\n\n\nstring: str = \"This string will be reversed ...\"\nprint(reverse_string(string)) # ... desrever eb lliw gnirts sihT\n\n\n"
  },
  {
    "path": "stack/stack-array-impl.py",
    "content": "class Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len(self._stack) == 0\n\n    def push(self, item):\n        self._stack.append(item)\n\n    def pop(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        # return self._stack[-1] # python way\n        return self._stack[len(self._stack) - 1]\n\n    def size(self) -> int:\n        return len(self._stack)\n\n\ndef reverse_string(s: str) -> str:\n    stack: Stack = Stack()\n    for character in s:\n        stack.push(character)\n\n    result: str = \"\"\n    while not stack.is_empty():\n        result += stack.pop()\n\n    return result\n\n\nstring: str = \"This string will be reversed ...\"\nprint(reverse_string(string)) # ... desrever eb lliw gnirts sihT\n\n\n"
  },
  {
    "path": "stack/stack-fixed-size-array-impl.py",
    "content": "from typing import Any, List\n\n\nclass Stack:\n\n    def __init__(self, capacity: int = 1) -> None:\n        self.capacity: int = capacity\n        self.stack: List[Any] = [None] * self.capacity\n        self.pointer: int = -1\n\n    def size(self) -> int:\n        return self.pointer + 1\n\n    def is_empty(self) -> bool:\n        return self.pointer == -1\n\n    def is_full(self) -> bool:\n        return self.pointer == self.capacity - 1\n\n    def push(self, item:Any):\n        if self.is_full():\n            raise Exception('Stack is full')\n        self.pointer += 1\n        self.stack[self.pointer] = item\n\n    def pop(self) -> Any:\n        if self.is_empty():\n            raise Exception('Stack is empty')\n        item = self.stack[self.pointer]\n        self.pointer -= 1\n        return item\n\n    def peek(self) -> Any:\n        if self.is_empty():\n            raise Exception('Stack is empty')\n        return self.stack[self.pointer]\n\n\ndef reverse_string(s: str) -> str:\n    stack: Stack = Stack(len(s))\n    for character in s:\n        stack.push(character)\n\n    result: str = \"\"\n    while not stack.is_empty():\n        result += stack.pop()\n\n    return result\n\n\nstring: str = \"This string will be reversed ...\"\nprint(reverse_string(string)) # ... desrever eb lliw gnirts sihT\n\n\n"
  },
  {
    "path": "stack/stack-linked-list-impl.py",
    "content": "class Node:\n    def __init__(self, key=None, next=None):\n        self.key = key\n        self.next = next\n\n\nclass Stack:\n    def __init__(self):\n        self._head: Node = None\n        self._count: int = 0\n\n    def is_empty(self) -> bool:\n        return self._head is None\n\n    def push(self, item):\n        self._head = Node(item, self._head)\n        self._count += 1\n\n    def pop(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        ret_value = self._head.key\n        self._head = self._head.next\n        self._count -= 1\n        return ret_value\n\n    def peek(self):\n        if self.is_empty():\n            raise Exception(\"Stack is empty\")\n        return self._head.key\n\n    def size(self) -> int:\n        return len(self._count)\n\n\ndef reverse_string(s: str) -> str:\n    stack: Stack = Stack()\n    for character in s:\n        stack.push(character)\n\n    result: str = \"\"\n    while not stack.is_empty():\n        result += stack.pop()\n\n    return result\n\n\nstring: str = \"This string will be reversed ...\"\nprint(reverse_string(string)) # ... desrever eb lliw gnirts sihT\n\n\n"
  },
  {
    "path": "stack/stack_two_queues.py",
    "content": "class Queue:\n    def __init__(self):\n        self._queue = []\n\n    def enqueue(self, item):\n        self._queue.insert(0, item)\n\n    def dequeue(self):\n        if self.is_empty():\n            raise Exception(\"Queue is empty\")\n        return self._queue.pop()\n\n    def size(self) -> int:\n        return len(self._queue)\n\n    def is_empty(self) -> bool:\n        return len(self._queue) == 0\n        \n    def peek(self) -> int:\n    \tif self.is_empty():\n            raise Exception(\"Queue is empty\")\n    \treturn self._queue[len(self._queue) - 1]\n\nclass MyStack:\n\n    def __init__(self):\n        self.input = Queue()\n        self.output = Queue()\n\n    def empty(self) -> bool:\n        return self.input.is_empty()\n\n    def push(self, item):\n        self.input.enqueue(item)\n\n    def pop(self):\n        if self.empty():\n            raise Exception(\"Stack is empty\")\n        while self.input.size() > 1 :\n            self.output.enqueue(self.input.dequeue())\n        \n        val = self.input.dequeue()\n        while not self.output.is_empty():\n            self.input.enqueue(self.output.dequeue())\n        \n        return val\n\n    def top(self):\n        if self.empty():\n            raise Exception(\"Stack is empty\")\n        while self.input.size() > 1 :\n            self.output.enqueue(self.input.dequeue())\n        \n        val = self.input.peek()\n        self.output.enqueue(self.input.dequeue())\n        while not self.output.is_empty():\n            self.input.enqueue(self.output.dequeue())\n        \n        return val\n\n    def size(self) -> int:\n        return self.input.size()\n\n        \n\n"
  },
  {
    "path": "stack_two_queues.py",
    "content": "from typing import Any, List\n\n\nclass Queue:\n\n    def __init__(self) -> None:\n        self.queue:List[Any] = []\n\n    def size(self) -> int:\n        return len(self.queue)\n\n    def is_empty(self) -> bool:\n        return len(self.queue) == 0\n\n    def enqueue(self, item:Any) -> None:\n        self.queue.insert(0, item)\n\n    def dequeue(self) -> Any:\n        if self.is_empty():\n            raise Exception('Queue is empty')\n        return self.queue.pop()\n\n\n\nclass Stack:\n\n    def __init__(self):\n        self.input: Queue = Queue()\n        self.output:Queue = Queue()\n        \n\n    def push(self, x: Any) -> None:\n        self.input.enqueue(x)\n        \n\n    def pop(self) -> Any:\n        if self.input.is_empty():\n            raise Exception('Queue is empty')\n        \n        while self.input.size() > 1:\n            self.output.enqueue(self.input.dequeue())\n            \n        item:Any = self.input.dequeue()\n\n        while not self.output.is_empty():\n            self.input.enqueue(self.output.dequeue())\n            \n        return item\n\n\n    def peek(self) -> Any:\n        if self.input.is_empty():\n            raise Exception('Queue is empty')\n            \n        while self.input.size() > 1:\n            self.output.enqueue(self.input.dequeue())\n            \n        item:Any = self.input.dequeue()\n        self.output.enqueue(item)\n        \n        while not self.output.is_empty():\n            self.input.enqueue(self.output.dequeue())\n        return item\n        \n\n    def is_empty(self) -> bool:\n        return self.input.is_empty()\n"
  },
  {
    "path": "substring-search/brute_force.py",
    "content": "\n\n# O(m * n)\n# m - length of the text\n# n - length of pattern\ndef search(text: str, pattern: str) -> int:\n    \n    t:int = 0\n    last_index: int = len(text) - len(pattern) + 1\n    \n    while t <= len(text) - len(pattern):\n        p:int = 0\n        while p < len(pattern):\n            if text[t + p] != pattern[p]:\n                break\n            else:\n                p += 1\n        if p == len(pattern):\n            return t\n        t += 1\n    \n    return -1\n\n\n        "
  },
  {
    "path": "trees/avl-tree.py",
    "content": "class TreeNode:\n    def __init__(self, key=None, value=None, parent=None, left=None, right=None,\n                 left_subtree_height: int = 0, right_subtree_height: int = 0, balance_factor: int = 0):\n        self.key = key\n        self.value = value\n        self.parent = parent\n        self.left = left\n        self.right = right\n        self.left_subtree_height: int = left_subtree_height\n        self.right_subtree_height: int = right_subtree_height\n        self.balance_factor : int = balance_factor\n\n    def has_left_child(self) -> bool:\n        return self.left is not None\n\n    def has_right_child(self) -> bool:\n        return self.right is not None\n\n    def has_both_children(self) -> bool:\n        return self.has_left_child() and self.has_right_child()\n\n    def is_leaf(self) -> bool:\n        return not self.has_left_child() and not self.has_right_child()\n\n    def is_root(self) -> bool:\n        return self.parent is None\n\n    def has_parent(self) -> bool:\n        return self.parent is not None\n\n    def is_left_child(self) -> bool:\n        return self.parent.left == self\n\n    def is_right_child(self) -> bool:\n        return self.parent.right == self\n\n    def find_min(self):\n        if self is None:\n            return None\n        if self.has_left_child():\n            return self.left.find_min()\n        else:\n            return self\n\n    def find_max(self):\n        if self is None:\n            return None\n        node = self\n        while node.right is not None:\n            node = node.right\n        return node\n\n\nclass AVLTree:\n    def __init__(self):\n        self.root: TreeNode = None\n        self.elements: int = 0\n\n    def size(self) -> int:\n        return self.elements\n\n    def is_empty(self) -> bool:\n        return self.root is None\n\n    def put(self, key, value):\n        if self.is_empty():\n            self.root = TreeNode(key, value)\n            self.elements += 1\n        else:\n            self._put(self.root, key, value)\n\n    def _put(self, root: TreeNode, key, value):\n        if root.key == key:\n            root.value = value\n        elif key < root.key:\n            if root.has_left_child():\n                self._put(root.left, key, value)\n            else:\n                root.left = TreeNode(key, value, root)\n                self.elements += 1\n                self._update_balance_factor(root)\n        else:\n            if root.has_right_child():\n                self._put(root.right, key, value)\n            else:\n                root.right = TreeNode(key, value, root)\n                self.elements += 1\n                self._update_balance_factor(root)\n\n    def get(self, key) -> TreeNode:\n        if self.is_empty():\n            return None\n        else:\n            return self._get(self.root, key)\n\n    def _get(self, root: TreeNode, key) -> TreeNode:\n        if root.key == key:\n            return root\n        elif key < root.key:\n            if root.has_left_child():\n                return self._get(root.left, key)\n            else:\n                return None\n        else:\n            if root.has_right_child():\n                return self._get(root.right, key)\n            else:\n                return None\n\n    def contains(self, key) -> bool:\n        if self.is_empty():\n            return None\n        found: bool = False\n        node: TreeNode = self.root\n        while node is not None and not found:\n            if node.key == key:\n                found = True\n            elif key < node.key:\n                node = node.left\n            else:\n                node = node.right\n        return found\n\n    def delete(self, key):\n        node_to_delete: TreeNode = self.get(key)\n        if node_to_delete is None:\n            return\n        if node_to_delete.is_root():\n            if node_to_delete.is_leaf():\n                self.root = None\n                self.elements -= 1\n            elif node_to_delete.has_both_children():\n                max_node: TreeNode = node_to_delete.left.find_max()\n                tmp_key = max_node.key\n                tmp_value = max_node.value\n                self.delete(tmp_key)\n                # keep pointer to that node, not root, root might change\n                node_to_delete.key = tmp_key\n                node_to_delete.value = tmp_value\n            else:\n                if node_to_delete.has_left_child():\n                    self.root = node_to_delete.left\n                else:\n                    self.root = node_to_delete.right\n                self.root.parent = None\n                self.elements -= 1\n        else:\n            parent: TreeNode = None\n            if node_to_delete.is_leaf():\n                parent = node_to_delete.parent\n                if node_to_delete.is_left_child():\n                    node_to_delete.parent.left = None\n                else:\n                    node_to_delete.parent.right = None\n                self.elements -= 1\n            elif node_to_delete.has_both_children():\n                max_node: TreeNode = node_to_delete.left.find_max()\n                tmp_key = max_node.key\n                tmp_value = max_node.value\n                self.delete(tmp_key)\n                node_to_delete.key = tmp_key\n                node_to_delete.value = tmp_value\n            elif node_to_delete.has_left_child():\n                parent = node_to_delete.parent\n                self.elements -= 1\n                if node_to_delete.is_left_child():\n                    node_to_delete.parent.left = node_to_delete.left\n                else:\n                    node_to_delete.parent.right = node_to_delete.left\n                node_to_delete.left.parent = node_to_delete.parent\n            else:\n                parent = node_to_delete.parent\n                self.elements -= 1\n                if node_to_delete.is_left_child():\n                    node_to_delete.parent.left = node_to_delete.right\n                else:\n                    node_to_delete.parent.right = node_to_delete.right\n                node_to_delete.right.parent = node_to_delete.parent\n            if parent is not None:\n                self._update_balance_factor(parent)\n\n    def find_min(self) -> TreeNode:\n        if self.is_empty():\n            return None\n        node: TreeNode = self.root\n        while node.left is not None:\n            node = node.left\n        return node\n\n    def find_max(self) -> TreeNode:\n        if self.is_empty():\n            return None\n        node: TreeNode = self.root\n        while node.right is not None:\n            node = node.right\n        return node\n\n    def _update_balance_factor(self, root: TreeNode):\n        old_balance_factor: int = root.balance_factor\n        if root.has_left_child():\n            root.left_subtree_height = max(root.left.left_subtree_height, root.left.right_subtree_height) + 1\n        else:\n            root.left_subtree_height = 0\n        if root.has_right_child():\n            root.right_subtree_height = max(root.right.left_subtree_height, root.right.right_subtree_height) + 1\n        else:\n            root.right_subtree_height = 0\n        root.balance_factor = root.left_subtree_height - root.right_subtree_height\n        if root.balance_factor < -1 or root.balance_factor > 1:\n            self._rebalance(root)\n            return\n        if root.balance_factor != old_balance_factor and root.has_parent():\n            self._update(root.parent)\n\n    def _rebalance(self, root: TreeNode):\n        if root.balance_factor < 0:\n            if root.right.balance_factor > 0:\n                self._rotate_right(root.right)\n            else:\n                self._rotate_left(root)\n        else:\n            if root.left.balance_factor < 0:\n                self._rotate_left(root.left)\n            else:\n                self._rotate_right(root)\n\n    def _rotate_left(self, root: TreeNode):\n        old_root: TreeNode = root\n        new_root: TreeNode = old_root.right\n        old_root.right = new_root.left\n        if new_root.has_left_child():\n            new_root.left.parent = old_root\n        new_root.parent = old_root.parent\n        if old_root.has_parent():\n            if old_root.is_left_child():\n                old_root.parent.left = new_root\n            else:\n                old_root.parent.right = new_root\n        else:\n            self.root = new_root\n        old_root.parent = new_root\n        new_root.left = old_root\n        self._update_balance_factor(old_root)\n\n    def _rotate_right(self, root: TreeNode):\n        old_root: TreeNode = root\n        new_root: TreeNode = old_root.left\n        old_root.left = new_root.right\n        if new_root.has_right_child():\n            new_root.right.parent = old_root\n        new_root.parent = old_root.parent\n        if old_root.has_parent():\n            if old_root.is_left_child():\n                old_root.parent.left = new_root\n            else:\n                old_root.parent.right = new_root\n        else:\n            self.root = new_root\n        old_root.parent = new_root\n        new_root.right = old_root\n        self._update_balance_factor(old_root)\n\n\n\n"
  },
  {
    "path": "trees/binary-heap.py",
    "content": "\n# min heap\nclass BinaryHeap:\n    def __init__(self):\n        self.pointer: int = 0\n        self.heap: list = [None]\n\n    def is_empty(self) -> bool:\n        return self.pointer == 0\n\n    def insert(self, item):\n        self.heap.append(item)\n        self.pointer += 1\n        self.perc_up(self.pointer)\n\n    def perc_up(self, index: int):\n        while index // 2 > 0:\n            if self.heap[index] < self.heap[index // 2]:\n                self.heap[index], self.heap[index // 2] = self.heap[index // 2], self.heap[index]\n            index = index // 2\n\n    def get_min(self):\n        if self.is_empty():\n            raise Exception(\"Heap is empty\")\n        return self.heap[1]\n\n    def delete_min(self):\n        if self.is_empty():\n            raise Exception(\"Heap is empty\")\n        ret_value: int = self.heap[1]\n        self.heap[1] = self.heap[self.pointer]\n        self.heap.pop()\n        self.pointer -= 1\n        self.perc_down(1)\n        return ret_value\n\n    def perc_down(self, index: int):\n        while index * 2 <= self.pointer:\n            swap_index: int = self.find_swap_index(index)\n            if self.heap[swap_index] < self.heap[index]:\n                self.heap[swap_index], self.heap[index] = self.heap[index], self.heap[swap_index]\n            index = swap_index\n\n    def find_swap_index(self, index: int) -> int:\n        if index * 2 + 1 > self.pointer:\n            return index * 2\n        else:\n            if self.heap[index * 2] <= self.heap[index * 2 + 1]:\n                return index * 2\n            else:\n                return index * 2 + 1\n\n    def build_heap(self, nums: list):\n        for n in nums:\n            self.insert(n)\n\n\nh: BinaryHeap = BinaryHeap()\nh.insert(10)\nh.insert(1)\nh.insert(3)\nprint(h.heap)  # [None, 1, 10, 3]\nprint(h.get_min())  # 1\n\nwhile not h.is_empty():\n    print(h.delete_min())\n"
  },
  {
    "path": "trees/binary-search-tree.py",
    "content": "class TreeNode:\n    def __init__(self, key=None, value=None, parent=None, left=None, right=None):\n        self.key = key\n        self.value = value\n        self.parent = parent\n        self.left = left\n        self.right = right\n\n    def has_left_child(self) -> bool:\n        return self.left is not None\n\n    def has_right_child(self) -> bool:\n        return self.right is not None\n\n    def has_both_children(self) -> bool:\n        return self.has_left_child() and self.has_right_child()\n\n    def is_leaf(self) -> bool:\n        return not self.has_left_child() and not self.has_right_child()\n\n    def is_root(self) -> bool:\n        return self.parent is None\n\n    def has_parent(self) -> bool:\n        return self.parent is not None\n\n    def is_left_child(self) -> bool:\n        if self.parent is None:\n            return False\n        return self.parent.left == self\n\n    def is_right_child(self) -> bool:\n        if self.parent is None:\n            return False\n        return self.parent.right == self\n\n    def find_min(self):\n        if self is None:\n            return None\n        if self.has_left_child():\n            return self.left.find_min()\n        else:\n            return self\n\n    def find_max(self):\n        if self is None:\n            return None\n        node = self\n        while node.right is not None:\n            node = node.right\n        return node\n\n\nclass BinarySearchTree:\n    def __init__(self):\n        self.root: TreeNode = None\n        self.elements: int = 0\n\n    def size(self) -> int:\n        return self.elements\n\n    def is_empty(self) -> bool:\n        return self.root is None\n\n    def put(self, key, value):\n        if self.is_empty():\n            self.root = TreeNode(key, value)\n            self.elements += 1\n        else:\n            self._put(self.root, key, value)\n\n    def _put(self, root: TreeNode, key, value):\n        if root.key == key:\n            root.value = value\n        elif key < root.key:\n            if root.has_left_child():\n                self._put(root.left, key, value)\n            else:\n                root.left = TreeNode(key, value, root)\n                self.elements += 1\n        else:\n            if root.has_right_child():\n                self._put(root.right, key, value)\n            else:\n                root.right = TreeNode(key, value, root)\n                self.elements += 1\n\n    def get(self, key) -> TreeNode:\n        if self.is_empty():\n            return None\n        else:\n            return self._get(self.root, key)\n\n    def _get(self, root: TreeNode, key) -> TreeNode:\n        if root.key == key:\n            return root\n        elif key < root.key:\n            if root.has_left_child():\n                return self._get(root.left, key)\n            else:\n                return None\n        else:\n            if root.has_right_child():\n                return self._get(root.right, key)\n            else:\n                return None\n\n    def contains(self, key) -> bool:\n        if self.is_empty():\n            return False\n        found: bool = False\n        node: TreeNode = self.root\n        while node is not None and not found:\n            if node.key == key:\n                found = True\n            elif key < node.key:\n                node = node.left\n            else:\n                node = node.right\n        return found\n\n    def delete(self, key):\n        node_to_delete: TreeNode = self.get(key)\n        if node_to_delete is None:\n            return\n        if node_to_delete.is_root():\n            if node_to_delete.is_leaf():\n                self.root = None\n                self.elements -= 1\n            elif node_to_delete.has_both_children():\n                max_node: TreeNode = node_to_delete.left.find_max()\n                tmp_key = max_node.key\n                tmp_value = max_node.value\n                self.delete(tmp_key)\n                node_to_delete.key = tmp_key\n                node_to_delete.value = tmp_value\n            else:\n                if node_to_delete.has_left_child():\n                    self.root = node_to_delete.left\n                else:\n                    self.root = node_to_delete.right\n                self.root.parent = None\n                self.elements -= 1\n        else:\n            if node_to_delete.is_leaf():\n                if node_to_delete.is_left_child():\n                    node_to_delete.parent.left = None\n                else:\n                    node_to_delete.parent.right = None\n                self.elements -= 1\n            elif node_to_delete.has_both_children():\n                max_node: TreeNode = node_to_delete.left.find_max()\n                tmp_key = max_node.key\n                tmp_value = max_node.value\n                self.delete(tmp_key)\n                node_to_delete.key = tmp_key\n                node_to_delete.value = tmp_value\n            elif node_to_delete.has_left_child():\n                self.elements -= 1\n                if node_to_delete.is_left_child():\n                    node_to_delete.parent.left = node_to_delete.left\n                else:\n                    node_to_delete.parent.right = node_to_delete.left\n                node_to_delete.left.parent = node_to_delete.parent\n            else:\n                self.elements -= 1\n                if node_to_delete.is_left_child():\n                    node_to_delete.parent.left = node_to_delete.right\n                else:\n                    node_to_delete.parent.right = node_to_delete.right\n                node_to_delete.right.parent = node_to_delete.parent\n\n    def find_min(self) -> TreeNode:\n        if self.is_empty():\n            return None\n        node: TreeNode = self.root\n        while node.left is not None:\n            node = node.left\n        return node\n\n    def find_max(self) -> TreeNode:\n        if self.is_empty():\n            return None\n        node: TreeNode = self.root\n        while node.right is not None:\n            node = node.right\n        return node\n"
  },
  {
    "path": "trees/class-representation.py",
    "content": "class TreeNode:\n    def __init__(self, key=None, left=None, right=None):\n        self.key = key\n        self.left = left\n        self.right = right\n\n    def insert_root_value(self, key=None):\n        self.key = key\n\n    def insert_left(self, key=None):\n        self.left = TreeNode(key, self.left)\n\n    def insert_right(self, key=None):\n        self.right = TreeNode(key, None, self.right)\n\n    def get_root_value(self):\n        return self.key\n\n    def get_left_child(self):\n        return self.left\n\n    def get_right_child(self):\n        return self.right\n\n\n# Write a function build_tree that returns a tree\n# using the list of lists functions that look like this :\n#      a\n#    /   \\\n#   b     c\n#    \\   / \\\n#     d e   f\n\n\ndef build_tree() -> TreeNode:\n    tree: TreeNode = TreeNode('a')\n    tree.insert_right('f')\n    tree.insert_right('c')\n    tree.get_right_child().insert_left('e')\n\n    tree.insert_left('b')\n    tree.get_left_child().insert_right('d')\n\n    return tree\n\n\nbinary_tree: TreeNode = build_tree()\n\n\ndef print_tree(tree: TreeNode):\n    if tree is not None:\n        print_tree(tree.get_left_child())\n        print(tree.key, end=\", \")\n        print_tree(tree.get_right_child())\n\n\nprint_tree(binary_tree)\n\n# ['a', \n#     ['b',\n#         [],\n#         ['d', [], []]],\n#     ['c',\n#         ['e', [], []],\n#         ['f', [], []]\n#     ]\n# ]\n"
  },
  {
    "path": "trees/list-representation.py",
    "content": "\ndef create_tree(root=None) -> list:\n    return [None, [], []]\n\n\ndef insert_root(tree: list, root=None):\n    tree[0] = root\n\n\ndef insert_left(tree: list, root=None):\n    left_child: list = tree.pop(1)\n    tree.insert(1, [root, left_child, []])\n\n\ndef insert_right(tree: list, root=None):\n    right_child: list = tree.pop(2)\n    tree.insert(2, [root, [], right_child])\n\n\ndef get_root(tree: list):\n    return tree[0]\n\n\ndef get_left_child(tree: list) -> list:\n    return tree[1]\n\n\ndef get_right_child(tree: list) -> list:\n    return tree[2]\n\n\n# Write a function build_tree that returns a tree\n# using the list of lists functions that look like this :\n#      a\n#    /   \\\n#   b     c\n#    \\   / \\\n#     d e   f\n\n\ndef build_tree() -> list:\n    tree: list = create_tree()\n    insert_root(tree, 'a')\n\n    insert_right(tree, 'f')\n    insert_right(tree, 'c')\n    insert_left(get_right_child(tree), 'e')\n\n    insert_left(tree, 'b')\n    insert_right(get_left_child(tree), 'd')\n\n    return tree\n\n\nbinary_tree: list = build_tree()\nprint(binary_tree)\n\n\n# ['a',\n#     ['b',\n#         [],\n#         ['d', [], []]],\n#     ['c',\n#         ['e', [], []],\n#         ['f', [], []]\n#     ]\n# ]\n"
  },
  {
    "path": "trees/parse-tree.py",
    "content": "\nfrom stack import Stack\nimport operator\nimport re\n\n\nclass TreeNode:\n    def __init__(self, key=None, left=None, right=None):\n        self.key = key\n        self.left = left\n        self.right = right\n\n    def insert_root_value(self, key=None):\n        self.key = key\n\n    def insert_left(self, key=None):\n        self.left = TreeNode(key, self.left)\n\n    def insert_right(self, key=None):\n        self.right = TreeNode(key, None, self.right)\n\n    def get_root_value(self):\n        return self.key\n\n    def get_left_child(self):\n        return self.left\n\n    def get_right_child(self):\n        return self.right\n\n\nopers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}\n\npattern = re.compile(\"[0-9]\")\n\nstring = \"( ( 10 + 5 ) * 3 )\"\n\n\ndef tree_parser(s: str) -> TreeNode:\n    arr: list = s.split()\n    stack: Stack = Stack()\n    node: TreeNode = TreeNode()\n    current_node: TreeNode = node\n    stack.push(node)\n    for e in arr:\n        if e == \"(\":\n            current_node.insert_left()\n            stack.push(current_node)\n            current_node = current_node.get_left_child()\n        elif e in \"+-*/\":\n            current_node.insert_root_value(e)\n            current_node.insert_right()\n            stack.push(current_node)\n            current_node = current_node.get_right_child()\n        elif pattern.match(e):\n            current_node.insert_root_value(int(e))\n            current_node = stack.pop()\n        elif e == \")\":\n            current_node = stack.pop()\n        else:\n            raise Exception()\n    return node\n\n\ntree_node: TreeNode = tree_parser(string)\n\n\ndef evaluate(node: TreeNode) -> int:\n    if node.get_left_child() is not None and node.get_right_child() is not None:\n        f = opers[node.get_root_value()]\n        return f(evaluate(node.get_left_child()), evaluate(node.get_right_child()))\n    else:\n        return node.get_root_value()\n\n\nprint(evaluate(tree_node))  # 45\n"
  },
  {
    "path": "trees/stack.py",
    "content": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n    def push(self, item):\n        self.items.append(item)\n\n    def pop(self):\n        return self.items.pop()\n\n    def peek(self):\n        return self.items[len(self.items) - 1]\n\n    def size(self):\n        return len(self.items)\n"
  },
  {
    "path": "trees/tree-traversal/functions.py",
    "content": "from treenode import TreeNode\n\nnode: TreeNode = TreeNode('a')\nnode.insert_left('b')\nnode.insert_left('c')\nnode.get_left_child().insert_left('f')\n\nnode.insert_right('k')\nnode.insert_right('j')\n\nprint(\"\\nInorder\")\nnode.inorder()\nprint(\"\\nPreorder\")\nnode.preorder()\nprint(\"\\nPostorder\")\nnode.postorder()\n\n# functions for tree traversal\n\n\ndef inorder(tree: TreeNode):\n    if tree is not None:\n        inorder(tree.left)\n        print(tree.key, end=\", \")\n        inorder(tree.right)\n\n\ndef preorder(tree: TreeNode):\n    if tree is not None:\n        print(tree.key, end=\", \")\n        preorder(tree.left)\n        preorder(tree.right)\n\n\ndef postorder(tree: TreeNode):\n    if tree is not None:\n        postorder(tree.left)\n        postorder(tree.right)\n        print(tree.key, end=\", \")\n\n\nprint(\"\\nInorder\")\ninorder(node)\nprint(\"\\nPreorder\")\npreorder(node)\nprint(\"\\nPostorder\")\npostorder(node)\n"
  },
  {
    "path": "trees/tree-traversal/inorder-traversal-example.py",
    "content": "\nfrom stack import Stack\nimport operator\nimport re\nfrom treenode import TreeNode\n\nopers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}\n\npattern = re.compile(\"[0-9]\")\n\nstring = \"( ( 10 + 5 ) * 3 )\"\n\n\ndef tree_parser(s: str) -> TreeNode:\n    arr: list = s.split()\n    stack: Stack = Stack()\n    node: TreeNode = TreeNode()\n    current_node: TreeNode = node\n    stack.push(node)\n    for e in arr:\n        if e == \"(\":\n            current_node.insert_left()\n            stack.push(current_node)\n            current_node = current_node.get_left_child()\n        elif e in \"+-*/\":\n            current_node.insert_root_value(e)\n            current_node.insert_right()\n            stack.push(current_node)\n            current_node = current_node.get_right_child()\n        elif pattern.match(e):\n            current_node.insert_root_value(int(e))\n            current_node = stack.pop()\n        elif e == \")\":\n            current_node = stack.pop()\n        else:\n            raise Exception()\n    return node\n\n\ntree_node: TreeNode = tree_parser(string)\n\n\ndef inorder(node: TreeNode) -> str:\n    ret_value: str = \"\"\n    if node.get_left_child() is not None:\n        ret_value += \"(\" + inorder(node.get_left_child())\n    ret_value += str(node.get_root_value())\n    if node.get_right_child() is not None:\n        ret_value += inorder(node.get_right_child()) + \")\"\n\n    return ret_value\n\n\nprint(inorder(tree_node))  # ((10+5)*3)\n\n"
  },
  {
    "path": "trees/tree-traversal/postorder-traversal-example.py",
    "content": "\nfrom stack import Stack\nimport operator\nimport re\nfrom treenode import TreeNode\n\nopers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}\n\npattern = re.compile(\"[0-9]\")\n\nstring = \"( ( 10 + 5 ) * 3 )\"\n\n\ndef tree_parser(s: str) -> TreeNode:\n    arr: list = s.split()\n    stack: Stack = Stack()\n    node: TreeNode = TreeNode()\n    current_node: TreeNode = node\n    stack.push(node)\n    for e in arr:\n        if e == \"(\":\n            current_node.insert_left()\n            stack.push(current_node)\n            current_node = current_node.get_left_child()\n        elif e in \"+-*/\":\n            current_node.insert_root_value(e)\n            current_node.insert_right()\n            stack.push(current_node)\n            current_node = current_node.get_right_child()\n        elif pattern.match(e):\n            current_node.insert_root_value(int(e))\n            current_node = stack.pop()\n        elif e == \")\":\n            current_node = stack.pop()\n        else:\n            raise Exception()\n    return node\n\n\ntree_node: TreeNode = tree_parser(string)\n\n\ndef postorder(node: TreeNode) -> str:\n    if node is None:\n        return None\n    left = postorder(node.get_left_child())\n    right = postorder(node.get_right_child())\n    if left is not None and right is not None:\n        f = opers[node.get_root_value()]\n        return f(left, right)\n    else:\n        return node.get_root_value()\n\n\nprint(postorder(tree_node))  # 45\n\n"
  },
  {
    "path": "trees/tree-traversal/preorder-traversal-example.py",
    "content": "\nfrom stack import Stack\nimport operator\nimport re\nfrom treenode import TreeNode\n\nopers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}\n\npattern = re.compile(\"[0-9]\")\n\nstring = \"( ( 10 + 5 ) * 3 )\"\n\n\ndef tree_parser(s: str) -> TreeNode:\n    arr: list = s.split()\n    stack: Stack = Stack()\n    node: TreeNode = TreeNode()\n    current_node: TreeNode = node\n    stack.push(node)\n    for e in arr:\n        if e == \"(\":\n            current_node.insert_left()\n            stack.push(current_node)\n            current_node = current_node.get_left_child()\n        elif e in \"+-*/\":\n            current_node.insert_root_value(e)\n            current_node.insert_right()\n            stack.push(current_node)\n            current_node = current_node.get_right_child()\n        elif pattern.match(e):\n            current_node.insert_root_value(int(e))\n            current_node = stack.pop()\n        elif e == \")\":\n            current_node = stack.pop()\n        else:\n            raise Exception()\n    return node\n\n\ntree_node: TreeNode = tree_parser(string)\n\n\ndef preorder(node: TreeNode, space: int = 0):\n    if node is not None:\n        print(\" \" * space, node.key)\n        preorder(node.get_left_child(), space + 2)\n        preorder(node.get_right_child(), space + 2)\n\n\npreorder(tree_node)\n\n"
  },
  {
    "path": "trees/tree-traversal/stack.py",
    "content": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n    def push(self, item):\n        self.items.append(item)\n\n    def pop(self):\n        return self.items.pop()\n\n    def peek(self):\n        return self.items[len(self.items) - 1]\n\n    def size(self):\n        return len(self.items)\n"
  },
  {
    "path": "trees/tree-traversal/treenode.py",
    "content": "class TreeNode:\n    def __init__(self, key=None, left=None, right=None):\n        self.key = key\n        self.left = left\n        self.right = right\n\n    def insert_root_value(self, key=None):\n        self.key = key\n\n    def insert_left(self, key=None):\n        self.left = TreeNode(key, self.left)\n\n    def insert_right(self, key=None):\n        self.right = TreeNode(key, None, self.right)\n\n    def get_root_value(self):\n        return self.key\n\n    def get_left_child(self):\n        return self.left\n\n    def get_right_child(self):\n        return self.right\n\n    def inorder(self):\n        if self.get_left_child() is not None:\n            self.get_left_child().inorder()\n        print(self.key, end=\", \")\n        if self.get_right_child() is not None:\n            self.get_right_child().inorder()\n\n    def preorder(self):\n        print(self.key, end=\", \")\n        if self.get_left_child() is not None:\n            self.get_left_child().preorder()\n        if self.get_right_child() is not None:\n            self.get_right_child().preorder()\n\n    def postorder(self):\n        if self.get_left_child() is not None:\n            self.get_left_child().postorder()\n        if self.get_right_child() is not None:\n            self.get_right_child().postorder()\n        print(self.key, end=\", \")\n"
  },
  {
    "path": "trie/trie.py",
    "content": "\r\nclass TrieNode:\r\n    def __init__(self, key, parent = None, children: dict = {}):\r\n        self.key = key\r\n        self.parent = parent\r\n        self.children:dict = {}\r\n        self.endchar: bool = False\r\n\r\nclass Trie:\r\n    def __init__(self):\r\n        self.root: TrieNode = TrieNode(None)\r\n\r\n    def insert(self, string: str):\r\n        current: TrieNode = self.root\r\n        for character in string:\r\n            if character not in current.children:\r\n                current.children[character] = TrieNode(character, current)\r\n            current = current.children[character]\r\n        current.endchar = True\r\n\r\n    def contains(self, string: str)->bool:\r\n        current: TrieNode = self.root\r\n        for character in string:\r\n            if character not in current.children:\r\n                current = None\r\n                break\r\n            current = current.children[character]\r\n        if current is None:\r\n            return False\r\n        return current.endchar\r\n\r\n    def delete(self, string: str):\r\n        current: TrieNode = self.root\r\n        for character in string:\r\n            if character not in current.children:\r\n                current = None\r\n                break\r\n            current = current.children[character]\r\n        if current is None:\r\n            return\r\n        current.endchar = False\r\n        parent: TrieNode = current.parent\r\n        while parent is not None and not current.endchar and len(current.children) == 0:\r\n            del(parent.children[current.key])\r\n            current = parent\r\n            parent = current.parent\r\n\r\n    def prefix(self, prefix: str)->list:\r\n        current: TrieNode = self.root\r\n        for character in prefix:\r\n            if character not in current.children:\r\n                current = None\r\n                break\r\n            current = current.children[character]\r\n        if current is None:\r\n            return\r\n        words: list = []\r\n        self.helper(current, words, prefix)\r\n        return words\r\n\r\n    def helper(self, node: TrieNode, words: list, currentWord: str):\r\n        if node is None:\r\n            return\r\n        if node.endchar:\r\n            words.append(currentWord)\r\n        for key in node.children:\r\n            self.helper(node.children[key], words, currentWord + key)\r\n\r\n    def allWords(self)->list:\r\n        words: list = []\r\n        self.helper(self.root, words, \"\")\r\n        return words\r\n\r\n    def count(self)->int:\r\n        return self.countHelper(self.root)\r\n\r\n    def countHelper(self, node: TrieNode)->int:\r\n        if node is None:\r\n            return 0\r\n        sum: int = 0\r\n        if node.endchar:\r\n            sum += 1\r\n        for character in node.children:\r\n            sum += self.countHelper(node.children[character])\r\n        return sum\r\n\r\ntrie = Trie()\r\ntrie.insert(\"javascript\")\r\ntrie.insert(\"java\")\r\ntrie.insert(\"scala\")\r\ntrie.insert(\"scale\")\r\ntrie.insert(\"scalable\")\r\ntrie.insert(\"perl\")\r\n\r\nprint(\"Contains 'javascript' : \", trie.contains(\"javascript\"))\r\nprint(\"Contains 'java' : \", trie.contains(\"java\"))\r\nprint(\"Contains 'ruby' : \", trie.contains(\"ruby\"))\r\n\r\n#trie.delete(\"java\")\r\ntrie.delete(\"javascript\")\r\n\r\n\r\nprint(\"Contains 'javascript' : \", trie.contains(\"javascript\"))\r\nprint(\"Contains 'java' : \", trie.contains(\"java\"))\r\nprint(\"Contains 'ruby' : \", trie.contains(\"ruby\"))\r\n\r\nprint(trie.prefix(\"scal\")) # ['scala', 'scalable', 'scale']\r\nprint(trie.prefix(\"java\")) # ['java']\r\n\r\nprint(\"All words\", trie.allWords()) # All words ['java', 'scala', 'scalable', 'scale', 'perl']\r\nprint(\"Count : \", trie.count())"
  }
]