128 files
157.9 KB
43.4k tokens
611 symbols
1 requests
Download .txt
Repository: ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python
Branch: master
Commit: 7ecd98a4537a
Files: 128
Total size: 157.9 KB

Directory structure:
gitextract_x7v3a_w5/

├── .gitignore
├── LICENSE
├── README.md
├── analysis/
│   ├── anagrams-linear-solution.py
│   ├── anagrams-loglinear-solution.py
│   ├── anagrams-quadratic-solution.py
│   ├── time-iterative-approach.py
│   └── time-noniterative-approach.py
├── deque/
│   ├── circular-deque.py
│   ├── deque.py
│   └── deque_linked_list_impl.py
├── graphs/
│   ├── bellman-ford/
│   │   └── graph.py
│   ├── bellman-ford-negative-weight-cycle/
│   │   └── graph.py
│   ├── breadth-first-search/
│   │   ├── graph.py
│   │   ├── main.py
│   │   └── queue.py
│   ├── cycle-detection/
│   │   ├── Cycle.md
│   │   ├── cycle-directed-graph/
│   │   │   ├── graph.py
│   │   │   └── main.py
│   │   └── cycle-undirected-graph/
│   │       ├── graph.py
│   │       └── main.py
│   ├── depth-first-search/
│   │   ├── depth-first-search/
│   │   │   ├── graph.py
│   │   │   ├── main.py
│   │   │   └── stack.py
│   │   └── depth-first-search-recursive/
│   │       ├── graph.py
│   │       └── main.py
│   ├── dijkstra/
│   │   ├── adjacency-list-impl/
│   │   │   ├── main.py
│   │   │   └── vertex.py
│   │   ├── matrix-impl/
│   │   │   ├── graph.py
│   │   │   ├── main.py
│   │   │   └── vertex.py
│   │   └── priority-queue-impl-adjacency-map/
│   │       ├── graph.py
│   │       ├── main.py
│   │       ├── priorityqueue.py
│   │       └── vertex.py
│   ├── is-graph-bipartite/
│   │   ├── graph.py
│   │   ├── main.py
│   │   └── queue.py
│   ├── kosarajus-algorithm/
│   │   ├── graph.py
│   │   ├── main.py
│   │   └── stack.py
│   ├── minimum-spanning-tree/
│   │   ├── breadth-first-search/
│   │   │   ├── graph.py
│   │   │   ├── main.py
│   │   │   └── queue.py
│   │   ├── kruskals-algorithm/
│   │   │   ├── graph.py
│   │   │   └── main.py
│   │   └── prims-algorithm/
│   │       ├── graph.py
│   │       ├── main.py
│   │       ├── priorityqueue.py
│   │       └── vertex.py
│   ├── topological-sorting/
│   │   ├── graph.py
│   │   └── main.py
│   └── union-find/
│       ├── number-of-connected-components/
│       │   └── graph.py
│       └── union-find-path-compression/
│           ├── graph.py
│           ├── main.py
│           └── vertex.py
├── hash-table/
│   ├── chaining.py
│   └── linear-probing.py
├── linked-lists/
│   ├── circular-doubly-linked-list/
│   │   ├── list.py
│   │   └── node.py
│   ├── circular-singly-linked-list/
│   │   ├── list.py
│   │   └── node.py
│   ├── doubly-linked-list/
│   │   ├── list.py
│   │   └── node.py
│   └── singly-linked-list/
│       ├── list.py
│       └── node.py
├── queue/
│   ├── circular-queue-fixed-size-array-impl.py
│   ├── queue-array-impl.py
│   ├── queue-fixed-size-array-impl.py
│   ├── queue-linked-list-impl.py
│   └── queue-two-stacks-impl.py
├── recursion/
│   ├── convert-number-iterative.py
│   ├── convert-number.py
│   ├── factorial.py
│   ├── fibonacci-iterative.py
│   ├── fibonacci-memoization.py
│   ├── fibonacci-recursive-worst-solution.py
│   ├── fibonacci-recursive.py
│   ├── fibonacci-sum-iterative.py
│   ├── fibonacci-sum-recursive.py
│   ├── maze.py
│   ├── palindrome.py
│   ├── reverse-linked-list-iterative-stack.py
│   ├── reverse-linked-list-iterative.py
│   ├── reverse-linked-list.py
│   ├── reverse-list.py
│   ├── reverse-string.py
│   ├── stack.py
│   ├── sum-numbers-binary-recursion.py
│   ├── sum-numbers-pointer.py
│   ├── sum-numbers-slicing.py
│   └── towers-of-hanoi.py
├── searching/
│   ├── binary-search-iterative.py
│   ├── binary-search-recursive-pointers.py
│   ├── binary-search-recursive.py
│   ├── sequential-search-ordered-list.py
│   └── sequential-search-unordered-list.py
├── sorting/
│   ├── bubble-sort.py
│   ├── insertion-sort.py
│   ├── merge-sort.py
│   ├── quicksort-return-new-array.py
│   ├── quicksort.py
│   ├── selection-sort.py
│   └── short-bubble.py
├── stack/
│   ├── examples/
│   │   ├── balanced-brackets.py
│   │   ├── number_converter.py
│   │   └── stack.py
│   ├── stack-array-impl-less-efficient.py
│   ├── stack-array-impl.py
│   ├── stack-fixed-size-array-impl.py
│   ├── stack-linked-list-impl.py
│   └── stack_two_queues.py
├── stack_two_queues.py
├── substring-search/
│   └── brute_force.py
├── trees/
│   ├── avl-tree.py
│   ├── binary-heap.py
│   ├── binary-search-tree.py
│   ├── class-representation.py
│   ├── list-representation.py
│   ├── parse-tree.py
│   ├── stack.py
│   └── tree-traversal/
│       ├── functions.py
│       ├── inorder-traversal-example.py
│       ├── postorder-traversal-example.py
│       ├── preorder-traversal-example.py
│       ├── stack.py
│       └── treenode.py
└── trie/
    └── trie.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/__pycache__/
/venv/
/.idea/

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Ivan Markovic

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Problem-Solving-with-Algorithms-and-Data-Structures-using-Python

I started the project by learning data structures and algorithms from a book *Problem Solving with Algorithms and Data Structures using Python*.
It does not contain everything from the book, 
nor is everything implemented in the same way,
but it also contains other data structures, algorithms and problems.

[Book website](https://runestone.academy/runestone/books/published/pythonds/index.html)

[Videos](https://teklern.blogspot.com/p/blog-page.html)

### [Algorithm analysis](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/analysis)
- [Anagrams - quadratic solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-quadratic-solution.py)
- [Anagrams - log linear solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-loglinear-solution.py)
- [Anagrams - linear solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-linear-solution.py)
- [Time - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/time-iterative-approach.py)
- [Time - non-iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/time-noniterative-approach.py)

### [Stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/stack)
- [Stack - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-array-impl.py)
- [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)
- [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)
- [Stack two queues](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack_two_queues.py)
- [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)
    - [Examples](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/stack/examples)
        - [Balanced brackets](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/examples/balanced-brackets.py)
        - [Number converter](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/examples/number_converter.py)
       

### [Queue](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/queue)
- [Queue - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-array-impl.py)
- [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)
- [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)
- [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)
- [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)

### [Deque](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/deque)
- [Deque - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/deque.py)
- [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)
- [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)

### [Linked lists](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists)
- [Singly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/singly-linked-list)
- [Doubly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/doubly-linked-list)
- [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)
- [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)

### [Recursion](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/recursion)
- [Convert number - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/convert-number.py)
- [Convert number - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/convert-number-iterative.py)
- [Factorial](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/factorial.py)
- [Fibonacci - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-iterative.py)
- [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)
- [Fibonacci - memoization](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-memoization.py)
- [Fibonacci - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-recursive.py)
- [Fibonacci sum recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-sum-recursive.py)
- [Fibonacci sum iterative](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-sum-iterative.py)
- [Maze - path finder](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/maze.py)
- [Palindrome](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/palindrome.py)
- [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)
- [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)
- [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)
- [Reverse list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-list.py)
- [Reverse string](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-string.py)
- [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)
- [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)
- [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)
- [Towers of Hanoi](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/towers-of-hanoi.py)

### [Searching](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/searching)
- [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)
- [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)
- [Binary search iterative](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-iterative.py)
- [Binary search recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-recursive.py)
- [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)

### [Hash table](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/hash-table)
- [Linear probing](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/hash-table/linear-probing.py)
- [Chaining](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/hash-table/chaining.py)

### [Trees](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trees)
- [List of lists representation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/list-representation.py)
- [Class representation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/class-representation.py)
- [Parse tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/parse-tree.py)
- [Tree traversal](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trees/tree-traversal)
    - [Methods](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/treenode.py)
    - [Functions](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/functions.py)
    - [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)
    - [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)
    - [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)
- [Binary Heap - min heap implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/binary-heap.py)  
- [Binary Search Tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/binary-search-tree.py)
- [AVL tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/avl-tree.py)

### [Sorting](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/sorting)
- [Bubble sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/bubble-sort.py)
- [Short bubble](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/short-bubble.py)
- [Insertion sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/insertion-sort.py)
- [Selection sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/selection-sort.py)
- [Merge sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/merge-sort.py)
- [Quicksort in place](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/quicksort.py)
- [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)

### [Graphs](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs)
- [Breadth first search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/breadth-first-search)
- [Depth first search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search)
    - [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)
    - [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)
- [Cycle detection](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection)
    - [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)
    - [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)
- [Topological sorting](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/topological-sorting)
- [Dijkstra algorithm - Shortest path](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra)
    - [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)
    - [Matrix implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/matrix-impl)
    - [Adjacency list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/adjacency-list-impl)
- [Bellman-Ford algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/bellman-ford)
- [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)
- [Minimum Spanning Tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree)
    - [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)
    - [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)
    - [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)
- [Is graph bipartite](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/is-graph-bipartite)
- [Union find](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find)
    - [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)
    - [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)
- [Kosaraju's algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/kosarajus-algorithm)
### [Trie](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trie)
- [Trie](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trie)


### [Substring search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/substring-search)
- [Brute force algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/substring-search/brute_force.py)


================================================
FILE: analysis/anagrams-linear-solution.py
================================================

# O(n)
def anagrams(string1: str, string2: str) -> bool:
    if len(string1) != len(string2):
        return False

    is_anagram: bool = True
    list1: list = [0] * 26
    list2: list = [0] * 26

    count_characters(string1, list1)
    count_characters(string2, list2)
    pos: int = 0
    while pos < len(list1) and is_anagram: # 26 steps max
        if list1[pos] != list2[pos]:
            is_anagram = False
        else:
            pos += 1

    return is_anagram


def count_characters(string: str, arr: list):
    for character in string:
        index: int = ord(character) - ord('a')
        arr[index] += 1


print(anagrams("python", "typhon"))
print(anagrams("abba", "baba"))
print(anagrams("abba", "abbb"))



================================================
FILE: analysis/anagrams-loglinear-solution.py
================================================

# O(nlogn)
def anagrams(string1: str, string2: str) -> bool:

    if len(string1) != len(string2):
        return False

    is_anagram: bool = True
    list1: list = list(string1)
    list2: list = list(string2)
    list1.sort() # sorting is O(nlogn)
    list2.sort() # sorting is O(nlogn)
    pos: int = 0
    # loop is O(n)
    while pos < len(list1) and is_anagram:
        if list1[pos] != list2[pos]:
            is_anagram = False
        else:
            pos += 1

    return is_anagram


print(anagrams("python", "typhon"))
print(anagrams("abba", "baba"))
print(anagrams("abba", "abbb"))



================================================
FILE: analysis/anagrams-quadratic-solution.py
================================================

# O(n2)
def anagrams(string1: str, string2: str) -> bool:
    if len(string1) != len(string2):
        return False

    is_anagram: bool = True

    list2: list = list(string2)
    pos1: int = 0
    while pos1 < len(string1) and is_anagram:
        character = string1[pos1]
        character_found: bool = False
        pos2: int = 0
        while pos2 < len(list2) and not character_found:
            if list2[pos2] == character:
                list2[pos2] = None
                character_found = True
            else:
                pos2 += 1
        if not character_found:
            is_anagram = False
        else:
            pos1 += 1

    return is_anagram


print(anagrams("python", "typhon"))
print(anagrams("abba", "baba"))
print(anagrams("abba", "abbb"))



================================================
FILE: analysis/time-iterative-approach.py
================================================
import time


def sum_nums(n: int) -> int:
    start = time.time()
    total: int = 0
    for i in range(n + 1):
        total += i
    end = time.time()
    print("For ", n, " numbers time is", end - start)
    return total


nums: list = [100, 10000, 100000, 1000000]
for n in nums:
    print(sum_nums(n))



================================================
FILE: analysis/time-noniterative-approach.py
================================================
import time


def sum_nums(n: int) -> int:
    start = time.time()
    total: int = n * (n + 1) / 2
    end = time.time()
    print("For ", n, " numbers time is", end - start)
    return total


nums: list = [100, 10000, 100000, 1000000]
for n in nums:
    print(sum_nums(n))



================================================
FILE: deque/circular-deque.py
================================================

from typing import Any, List


class Deque:

    def __init__(self, capacity: int = 10) -> None:
        self.capacity: int = capacity
        self.length: int = 0
        self._deque: List[Any] = [None] * self.capacity
        self.front: int = -1
        self.rear: int = -1

    def is_empty(self) -> bool:
        return self.front == -1

    def is_full(self) -> bool:
        return (self.rear + 1) % self.capacity == self.front

    def size(self) -> int:
        return self.length

    def add_front(self, item: Any):
        if self.is_full():
            raise Exception('Deque is full')
        if self.is_empty(): # self.rear == -1
            self.front = self.rear = 0
        elif self.front == 0:
            self.front = self.capacity - 1
        else:
            self.front -= 1
        self._deque[self.front] = item
        self.length += 1

    def add_rear(self, item: Any):
        if self.is_full():
            raise Exception('Deque is full')
        if self.is_empty(): # self.rear == -1
            self.front = self.rear = 0
        else:
            self.rear = (self.rear + 1) % self.capacity
        self._deque[self.rear] = item
        self.length += 1

    def remove_front(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        item: Any = self._deque[self.front]
        if self.front == self.rear:
            self.front = self.rear = -1
        else:
            self.front = (self.front + 1) % self.capacity
        self.length -= 1
        return item

    def remove_rear(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        item: Any = self._deque[self.rear]
        if self.rear == self.front:
            self.rear = self.front = -1
        elif self.rear == 0:
            self.rear = self.capacity - 1
        else:
            self.rear -= 1
        self.length -= 1
        return item

    def get_front(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        return self._deque[self.front]

    def get_rear(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        return self._deque[self.rear]


def is_palindrome(string:str='') -> bool:
    d: Deque = Deque()

    for character in string:
        d.add_rear(character)

    palindrome: bool = True
    while d.size() > 1 and palindrome:
        if d.remove_front() != d.remove_rear():
            palindrome = False
    
    return palindrome

print(is_palindrome('radar'))
print(is_palindrome('radr'))
print(is_palindrome('r'))
print(is_palindrome(''))


================================================
FILE: deque/deque.py
================================================

from typing import Any, List


class Deque:

    def __init__(self) -> None:
        self._deque: List[Any] = []

    def is_empty(self) -> bool:
        return len(self._deque) == 0

    def size(self) -> int:
        return len(self._deque)

    def add_front(self, item: Any):
        self._deque.insert(0, item)

    def add_rear(self, item: Any):
        self._deque.append(item)

    def remove_front(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        return self._deque.pop(0)

    def remove_rear(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        return self._deque.pop()



def is_palindrome(string:str='') -> bool:
    d: Deque = Deque()

    for character in string:
        d.add_rear(character)

    palindrome: bool = True
    while d.size() > 1 and palindrome:
        if d.remove_front() != d.remove_rear():
            palindrome = False
    
    return palindrome

print(is_palindrome('radar'))
print(is_palindrome('radr'))
print(is_palindrome('r'))
print(is_palindrome(''))


================================================
FILE: deque/deque_linked_list_impl.py
================================================


from typing import List, Any


class ListNode:

    def __init__(self, key:Any = None, prev:'ListNode' = None, next:'ListNode' = None):
        self.key:Any = key
        self.prev:'ListNode' = prev
        self.next:'ListNode' = next


class Deque:

    def __init__(self):
        self._head:ListNode = None
        self._tail:ListNode = None
        self._length:int = 0


    def size(self) -> int:
        return self._length
    

    def is_empty(self) -> bool:
        return self._length == 0
    

    def add_front(self, e:Any) -> None:
        if self.is_empty():
            self._head = self._tail = ListNode(e)
        else:
            self._head = ListNode(e, None, self._head)
            self._head.next.prev = self._head
        self._length += 1


    def add_rear(self, e:Any) -> None:
        if self.is_empty():
            self._head = self._tail = ListNode(e)
        else:
            self._tail.next = ListNode(e, self._tail, None)
            self._tail = self._tail.next
        self._length += 1


    def remove_front(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        e:Any = self._head.key
        if self._head == self._tail:
            self._head = self._tail = None
        else:
            self._head = self._head.next
            self._head.prev = None
        self._length -= 1
        return e
    

    def remove_rear(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        e:Any = self._tail.key
        if self._head == self._tail:
            self._head = self._tail = None
        else:
            self._tail = self._tail.prev
            self._tail.next = None
        self._length -= 1
        return e
    

    def get_front(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        return self._head.key
    

    def remove_rear(self) -> Any:
        if self.is_empty():
            raise Exception('Deque is empty')
        return self._tail.key


================================================
FILE: graphs/bellman-ford/graph.py
================================================

#  Bellman-Ford algorithm
#  Find shortest paths from one vertex,
#  to all other vertices in weighted graph.
#  Runtime O(V*E)

from typing import Set, Dict, List, Tuple


class Graph:


    def __init__(self) -> None:
        self.vertices:Set[str] = set()
        self.edges:List[Tuple[str, str, int]] = list()
        self.prev:Dict[str, str] = dict()
        self.distances:Dict[str, int] = dict()


    def add_vertex(self, label:str) -> None:
        self.vertices.add(label)
        self.prev[label] = None
        self.distances[label] = None


    def add_edge(self, v1:str, v2:str, distance:int) -> None:
        self.edges.append((v1, v2, distance))


    def bellman_ford(self, label:str) -> None:
        self.distances[label] = 0

        for _ in range(len(self.vertices) - 1):

            for v1, v2, distance in self.edges:
                if self.distances[v1] is None:
                    continue
                if self.distances[v2] is None or self.distances[v2] > self.distances[v1] + distance:
                    self.distances[v2] = self.distances[v1] + distance
                    self.prev[v2] = v1

        # Check for negative-weight cycles
        for v1, v2, distance in self.edges:
            if self.distances[v1] is not None and self.distances[v2] > self.distances[v1] + distance:
                raise ValueError("Graph contains a negative-weight cycle")

        self._print_paths(label)


    def _print_paths(self, label:str) -> None:
        for v in self.vertices:
            if v == label:
                continue
            if self.distances[v] is not None:
                print(f'Path from {label} to {v} is {self._return_path(v)} and distance is {self.distances[v]}')
            else:
                print(f'No path from {label} to {v}')


    def _return_path(self, label:str) -> str:
        if self.prev[label] is None:
            return label
        return self._return_path(self.prev[label]) + ' -> ' + label
    


g = Graph()
for v in ['A', 'B', 'C', 'D']:
    g.add_vertex(v)

g.add_edge('A', 'B', 1)
g.add_edge('B', 'C', 3)
g.add_edge('A', 'C', 10)
g.add_edge('C', 'D', 2)
g.add_edge('D', 'B', 4)

g.bellman_ford('A')



================================================
FILE: graphs/bellman-ford-negative-weight-cycle/graph.py
================================================

#  Bellman-Ford algorithm
#  Find shortest paths from one vertex,
#  to all other vertices in weighted graph.
#  Runtime O(V*E)

# Negative weight cycle will be found with one more loop through edges
# If there is a need to update distance than it is a negative weight cycle

class Graph:
    def __init__(self):
        self.vertices: list = []
        self.edges: list = []
        self.distance: dict = {}
        self.prev: dict = {}

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.distance[label] = None
        self.prev[label] = None

    def add_edge(self, label1: str, label2: str, weight: int):
        self.edges.append([label1, label2, weight])

    def bellman_ford(self, source: str):
        self.distance[source] = 0

        for _ in range(len(self.vertices)):

            for edge in self.edges:
                label1: str = edge[0]
                label2: str = edge[1]
                weight: int = edge[2]

                if self.distance[label1] is None:
                    continue
                if self.distance[label2] is None:
                    self.distance[label2] = self.distance[label1] + weight
                    self.prev[label2] = label1
                    continue
                if self.distance[label1] + weight < self.distance[label2]:
                    self.distance[label2] = self.distance[label1] + weight
                    self.prev[label2] = label1
                    continue

        for edge in self.edges:
            label1: str = edge[0]
            label2: str = edge[1]
            weight: int = edge[2]

            if self.distance[label1] is None:
                continue
            if self.distance[label2] is None:
                continue
            if self.distance[label1] + weight < self.distance[label2]:
                print(f'Negative weight cycle from {label1} to {label2}')

    def print_distances(self, source: str):
        for v in self.vertices:
            if v != source:
                distance: int = self.distance[v]
                print(f'Distance from {source} to {v} is {distance}')

================================================
FILE: graphs/breadth-first-search/graph.py
================================================
from queue import Queue


class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.prev: dict = {}
        self.distance: dict = {}
        self.colors: dict = {}

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.prev[label] = None
        self.distance[label] = 0
        self.colors[label] = "white"

    def add_edge(self, label1: str, label2: str):
        self.adjacency_list[label1].append(label2)
        self.adjacency_list[label2].append(label1)

    def bfs(self, label: str):
        q: Queue = Queue()
        q.enqueue(label)
        self.colors[label] = "gray"
        while not q.is_empty():
            tmp: str = q.dequeue()
            for neighbour in self.adjacency_list[tmp]:
                if self.colors[neighbour] == "white":
                    self.prev[neighbour] = tmp
                    self.distance[neighbour] = self.distance[tmp] + 1
                    self.colors[neighbour] = "gray"
                    q.enqueue(neighbour)
            self.colors[tmp] = "black"

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label


================================================
FILE: graphs/breadth-first-search/main.py
================================================
from graph import Graph

graph = Graph()

my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
# add vertices
for i in range(len(my_vertices)):
    graph.add_vertex(my_vertices[i])

graph.add_edge('A', 'B')
graph.add_edge('A', 'C')
graph.add_edge('A', 'D')
graph.add_edge('C', 'D')
graph.add_edge('C', 'G')
graph.add_edge('D', 'G')
graph.add_edge('D', 'H')
graph.add_edge('B', 'E')
graph.add_edge('B', 'F')
graph.add_edge('E', 'I')

graph.bfs("A")
print(graph.return_path("H"))


================================================
FILE: graphs/breadth-first-search/queue.py
================================================
class Queue:
    def __init__(self):
        self.queue = []

    def enqueue(self, item):
        self.queue.insert(0, item)

    def dequeue(self):
        return self.queue.pop()

    def size(self):
        return len(self.queue)

    def is_empty(self):
        return self.queue == []

================================================
FILE: graphs/cycle-detection/Cycle.md
================================================


#Cycle - vertex is reachable from itself. 


##Undirected graph

'''
edge(u, v)
'''

- v is in the stack, visited but not explored

- v is ancestor of u, but not parent

##Directed graph

'''
edge(u, v)
'''

- v is in the stack, visited but not explored

- v is ancestor of u

================================================
FILE: graphs/cycle-detection/cycle-directed-graph/graph.py
================================================

class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.prev: dict = {}
        self.distance: dict = {}
        self.colors: dict = {}
        self.entry: dict = {}
        self.exit: dict = {}
        self.time: int = 0

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.prev[label] = None
        self.distance[label] = 0
        self.colors[label] = "white"

    def add_edge(self, label1: str, label2: str):
        self.adjacency_list[label1].append(label2)

    def dfs(self, label: str):
        self.colors[label] = "gray"
        self.time += 1
        self.entry[label] = self.time
        for neighbour in self.adjacency_list[label]:
            if self.colors[neighbour] == "white":
                self.prev[neighbour] = label
                self.distance[neighbour] = self.distance[label] + 1
                self.dfs(neighbour)
            if self.colors[neighbour] == "gray":
                print("Cycle", label, " - ", neighbour)
        self.colors[label] = "black"
        self.time += 1
        self.exit[label] = self.time

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label


================================================
FILE: graphs/cycle-detection/cycle-directed-graph/main.py
================================================
from graph import Graph

graph: Graph = Graph()


vertices = ["a", "b", "c", "d"]
for vertex in vertices:
    graph.add_vertex(vertex)

graph.add_edge("a", "b")
graph.add_edge("b", "c")
graph.add_edge("c", "d")
graph.add_edge("d", "b")

graph.dfs("a")



================================================
FILE: graphs/cycle-detection/cycle-undirected-graph/graph.py
================================================

class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.prev: dict = {}
        self.distance: dict = {}
        self.colors: dict = {}
        self.entry: dict = {}
        self.exit: dict = {}
        self.time: int = 0

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.prev[label] = None
        self.distance[label] = 0
        self.colors[label] = "white"

    def add_edge(self, label1: str, label2: str):
        self.adjacency_list[label1].append(label2)
        self.adjacency_list[label2].append(label1)

    def dfs(self, label: str):
        self.colors[label] = "gray"
        self.time += 1
        self.entry[label] = self.time
        for neighbour in self.adjacency_list[label]:
            if self.colors[neighbour] == "white":
                self.prev[neighbour] = label
                self.distance[neighbour] = self.distance[label] + 1
                self.dfs(neighbour)
            if self.colors[neighbour] == "gray" and self.prev[label] != neighbour:
                print("Cycle", label, " - ", neighbour)
        self.colors[label] = "black"
        self.time += 1
        self.exit[label] = self.time

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label


================================================
FILE: graphs/cycle-detection/cycle-undirected-graph/main.py
================================================
from graph import Graph

graph: Graph = Graph()


vertices = ["a", "b", "c", "d"]
for vertex in vertices:
    graph.add_vertex(vertex)

graph.add_edge("a", "b")
graph.add_edge("b", "c")
graph.add_edge("c", "d")
# graph.add_edge("d", "b")

graph.dfs("a")



================================================
FILE: graphs/depth-first-search/depth-first-search/graph.py
================================================
from stack import Stack


class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.prev: dict = {}
        self.distance: dict = {}
        self.colors: dict = {}
        self.entry: dict = {}
        self.exit: dict = {}
        self.time: int = 0

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.prev[label] = None
        self.distance[label] = 0
        self.colors[label] = "white"

    def add_edge(self, label1: str, label2: str):
        self.adjacency_list[label1].append(label2)
        self.adjacency_list[label2].append(label1)

    def dfs(self, label: str):
        s: Stack = Stack()
        s.push(label)
        self.colors[label] = "gray"
        self.time += 1
        self.entry[label] = self.time
        while not s.is_empty():
            tmp: str = s.peek()
            neighbour: str = self.find_unvisited_neighbour(tmp)
            if neighbour is not None:
                self.prev[neighbour] = tmp
                self.distance[neighbour] = self.distance[tmp] + 1
                self.colors[neighbour] = "gray"
                self.time += 1
                self.entry[neighbour] = self.time
                s.push(neighbour)
            else:
                s.pop()
                self.time += 1
                self.exit[tmp] = self.time
                self.colors[tmp] = "black"

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label

    def find_unvisited_neighbour(self, tmp) -> str:
        for n in self.adjacency_list[tmp]:
            if self.colors[n] == "white":
                return n
        return None


================================================
FILE: graphs/depth-first-search/depth-first-search/main.py
================================================
from graph import Graph

graph = Graph()

my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
# add vertices
for i in range(len(my_vertices)):
    graph.add_vertex(my_vertices[i])

graph.add_edge('A', 'B')
graph.add_edge('A', 'C')
graph.add_edge('A', 'D')
graph.add_edge('C', 'D')
graph.add_edge('C', 'G')
graph.add_edge('D', 'G')
graph.add_edge('D', 'H')
graph.add_edge('B', 'E')
graph.add_edge('B', 'F')
graph.add_edge('E', 'I')

graph.dfs("A")
print(graph.return_path("H"))


================================================
FILE: graphs/depth-first-search/depth-first-search/stack.py
================================================
class Stack:
    def __init__(self):
        self.items = []

    def is_empty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items) - 1]

    def size(self):
        return len(self.items)

================================================
FILE: graphs/depth-first-search/depth-first-search-recursive/graph.py
================================================

class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.prev: dict = {}
        self.distance: dict = {}
        self.colors: dict = {}
        self.entry: dict = {}
        self.exit: dict = {}
        self.time: int = 0

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.prev[label] = None
        self.distance[label] = 0
        self.colors[label] = "white"

    def add_edge(self, label1: str, label2: str):
        self.adjacency_list[label1].append(label2)
        self.adjacency_list[label2].append(label1)

    def dfs(self, label: str):
        self.colors[label] = "gray"
        self.time += 1
        self.entry[label] = self.time
        for neighbour in self.adjacency_list[label]:
            if self.colors[neighbour] == "white":
                self.prev[neighbour] = label
                self.distance[neighbour] = self.distance[label] + 1
                self.dfs(neighbour)
        self.colors[label] = "black"
        self.time += 1
        self.exit[label] = self.time

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label


================================================
FILE: graphs/depth-first-search/depth-first-search-recursive/main.py
================================================
from graph import Graph

graph = Graph()

my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
# add vertices
for i in range(len(my_vertices)):
    graph.add_vertex(my_vertices[i])

graph.add_edge('A', 'B')
graph.add_edge('A', 'C')
graph.add_edge('A', 'D')
graph.add_edge('C', 'D')
graph.add_edge('C', 'G')
graph.add_edge('D', 'G')
graph.add_edge('D', 'H')
graph.add_edge('B', 'E')
graph.add_edge('B', 'F')
graph.add_edge('E', 'I')

graph.dfs("A")
print(graph.return_path("H"))


================================================
FILE: graphs/dijkstra/adjacency-list-impl/main.py
================================================

from typing import List, Dict, Set

from vertex import Vertex


class Graph:

    def __init__(self, capacity :int =10):
        self.capacity :int = capacity
        self.vertices :Dict[str, Vertex] = dict()
        self.prev :Dict[str, str] = dict()
        self.adjacency_list :Dict[str, List[Vertex]] = dict()
        self.visited :Set[str] = set()



    def add_vertex(self, label :str, weight :int = float('inf')) -> None:
        v :Vertex = Vertex(label, weight)
        self.vertices[v.label] = v
        self.adjacency_list[label] = list()
        self.prev[label] = None


    def add_edge(self, label1 :str, label2 :str, weight :int) -> None:
        v :Vertex = Vertex(label2, weight)
        self.adjacency_list[label1].append(v)


    def dijkstra(self, label :str) -> None:
        v :Vertex = self.vertices[label]
        v.weight = 0

        while v is not None:
            for n in self.adjacency_list[v.label]:
                o :Vertex = self.vertices[n.label]
                if v.weight + n.weight < o.weight:
                    o.weight = v.weight + n.weight
                    self.prev[o.label] = v.label
            self.visited.add(v.label)
            v = self._find_cheapest_vertex()


    def show_path(self, label :str) -> str:
        if self.prev[label] is None:
            return label
        return self.show_path(self.prev[label]) + '->' + label


    def _find_cheapest_vertex(self) -> Vertex:
        vertex :Vertex = None
        for v in self.vertices.values():
            if v.label not in self.visited:
                if vertex is None:
                    vertex = v
                elif vertex.weight > v.weight:
                    vertex = v

        return vertex



graph: Graph = Graph()

graph.add_vertex("START")
graph.add_vertex("A")
graph.add_vertex("C")
graph.add_vertex("B")
graph.add_vertex("D")
graph.add_vertex("END")

graph.add_edge("START", "A", 0)
graph.add_edge("START", "C", 2)
graph.add_edge("A", "B", 18)
graph.add_edge("A", "D", 15)
graph.add_edge("C", "B", 3)
graph.add_edge("C", "D", 10)
graph.add_edge("B", "END", 150)
graph.add_edge("D", "END", 15)
graph.dijkstra("START")

print(graph.show_path("END"))







================================================
FILE: graphs/dijkstra/adjacency-list-impl/vertex.py
================================================


class Vertex:

    def __init__(self, label :str, weight :int = float('inf')):
        self.label :str = label
        self.weight :int = weight

================================================
FILE: graphs/dijkstra/matrix-impl/graph.py
================================================
from vertex import Vertex


class Graph:
    def __init__(self, size: int = 10):
        self.size: int = size
        self.index: int = 0
        self.vertices_list: list = [None] * self.size
        self.vertices: dict = {}
        self.adjacency_matrix: list = [[None for i in range(self.size)] for j in range(self.size)]
        self.prev: dict = {}
        self.visited: dict = {}

    def add_vertex(self, label: str):
        if self.index == self.size:  # matrix is full
            return
        vertex: Vertex = Vertex(label, float("inf"), self.index)
        self.vertices_list[self.index] = vertex
        self.vertices[vertex.label] = vertex
        self.index += 1
        self.prev[vertex.label] = None
        self.visited[vertex.label] = False

    def add_edge(self, label1: str, label2: str, weight: int):
        index1: int = self.vertices[label1].index
        index2: int = self.vertices[label2].index
        self.adjacency_matrix[index1][index2] = weight

    def dijkstra(self, label: str):
        current_vertex: Vertex = self.vertices[label]
        current_vertex.weight = 0
        while current_vertex is not None:
            self.visited[current_vertex.label] = True
            for i in range(self.index):
                if self.adjacency_matrix[current_vertex.index][i] is not None:
                    weight: int = self.adjacency_matrix[current_vertex.index][i]
                    neighbour: Vertex = self.vertices_list[i]
                    if current_vertex.weight + weight < neighbour.weight:
                        neighbour.weight = current_vertex.weight + weight
                        self.prev[neighbour.label] = current_vertex.label
            current_vertex = self.find_minimum_weight_vertex()

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label

    def find_minimum_weight_vertex(self):
        vertex: Vertex = None
        for label in self.vertices:
            if not self.visited[label]:
                if vertex is None:
                    vertex = self.vertices[label]
                else:
                    if vertex.weight > self.vertices[label].weight:
                        vertex = self.vertices[label]
        return vertex





================================================
FILE: graphs/dijkstra/matrix-impl/main.py
================================================
from graph import Graph


graph: Graph = Graph()

graph.add_vertex("START")
graph.add_vertex("A")
graph.add_vertex("C")
graph.add_vertex("B")
graph.add_vertex("D")
graph.add_vertex("END")

graph.add_edge("START", "A", 0)
graph.add_edge("START", "C", 2)
graph.add_edge("A", "B", 18)
graph.add_edge("A", "D", 15)
graph.add_edge("C", "B", 3)
graph.add_edge("C", "D", 10)
graph.add_edge("B", "END", 150)
graph.add_edge("D", "END", 15)
graph.dijkstra("START")

print(graph.return_path("END"))


================================================
FILE: graphs/dijkstra/matrix-impl/vertex.py
================================================
class Vertex:
    def __init__(self, label: str  = None, weight: int = float("inf"), index: int = None):
        self.label: str = label
        self.weight: int = weight
        self.index: int = index



================================================
FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/graph.py
================================================
from vertex import Vertex
from priorityqueue import PriorityQueue


class Graph:
    def __init__(self):
        self._vertices: dict = {}
        self._adjacency_map: dict = {}
        self._prev: dict = {}

    def add_vertex(self, label: str):
        v: Vertex = Vertex(label)
        self._vertices[label] = v
        self._adjacency_map[label]: list = []
        self._prev[label] = None

    def add_edge(self, label1: str, label2: str, weight: int):
        self._adjacency_map[label1].append(Vertex(label2, weight))

    def dijkstra(self, label: str):
        self._vertices[label].weight = 0
        pq: PriorityQueue = PriorityQueue()
        for label in self._vertices:
            pq.insert(self._vertices[label])
        while not pq.is_empty():
            current: Vertex = pq.delete_min()
            for neighbour in self._adjacency_map[current.label]:
                v: Vertex = self._vertices[neighbour.label]
                if current.weight + neighbour.weight < v.weight:
                    v.weight = current.weight + neighbour.weight
                    self._prev[v.label] = current.label
                    pq.decrease_key(v.key)

    def show_path(self, label: str) -> str:
        if self._prev[label] is None:
            return label
        else:
            return self.show_path(self._prev[label]) + " -> " + label










================================================
FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/main.py
================================================
from graph import Graph


graph: Graph = Graph()

graph.add_vertex("START")
graph.add_vertex("A")
graph.add_vertex("C")
graph.add_vertex("B")
graph.add_vertex("D")
graph.add_vertex("END")

graph.add_edge("START", "A", 0)
graph.add_edge("START", "C", 2)
graph.add_edge("A", "B", 18)
graph.add_edge("A", "D", 15)
graph.add_edge("C", "B", 3)
graph.add_edge("C", "D", 10)
graph.add_edge("B", "END", 150)
graph.add_edge("D", "END", 15)
graph.dijkstra("START")

print(graph.show_path("END"))



================================================
FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/priorityqueue.py
================================================
from vertex import Vertex


class PriorityQueue:
    def __init__(self):
        self.pq: list = [None]
        self._pointer: int = 0

    def is_empty(self) -> bool:
        return self._pointer == 0

    def insert(self, vertex: Vertex):
        self.pq.append(vertex)
        self._pointer += 1
        vertex.key = self._pointer
        self._perc_up(self._pointer)

    def _perc_up(self, pointer: int):
        while pointer // 2 > 0:
            if self.pq[pointer // 2].weight > self.pq[pointer].weight:
                self.pq[pointer // 2], self.pq[pointer] = self.pq[pointer], self.pq[pointer // 2]
                self.pq[pointer // 2].key = pointer // 2
                self.pq[pointer].key = pointer
            pointer = pointer // 2

    def decrease_key(self, pointer: int):
        self._perc_up(pointer)

    def delete_min(self) -> Vertex:
        if self.is_empty():
            raise Exception("Priority queue is empty")
        v: Vertex = self.pq[1]
        self.pq[1] = self.pq[self._pointer]
        self.pq[1].key = 1
        self.pq.pop()
        self._pointer -= 1
        self._perc_down(1)
        return v

    def _perc_down(self, pointer: int):
        while pointer * 2 <= self._pointer:
            min_index: int = self._find_swap_index(pointer)
            if self.pq[pointer].weight > self.pq[min_index].weight:
                self.pq[pointer], self.pq[min_index] = self.pq[min_index], self.pq[pointer]
                self.pq[pointer].key = pointer
                self.pq[min_index].key = min_index
            pointer = min_index

    def _find_swap_index(self, pointer: int) -> int:
        if pointer * 2 + 1 > self._pointer:
            return pointer * 2
        else:
            if self.pq[pointer * 2].weight <= self.pq[pointer * 2 + 1].weight:
                return pointer * 2
            else:
                return pointer * 2 + 1


================================================
FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/vertex.py
================================================
class Vertex:
    def __init__(self, label: str = None, weight: int = float("inf"), key: int = None):
        self.label: str = label
        self.weight: int = weight
        self.key: int = key


================================================
FILE: graphs/is-graph-bipartite/graph.py
================================================
from queue import Queue


class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.color: dict = {}

    def add_vertex(self, label: str = None):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.color[label] = None

    def add_edge(self, label1: str = None, label2: str = None):
        self.adjacency_list[label1].append(label2)
        self.adjacency_list[label2].append(label1)

    def bipartite_check(self) -> bool:
        for vertex in self.vertices:
            if self.color[vertex] is not None:
                continue
            q: Queue = Queue()
            self.color[vertex] = "red"
            q.enqueue(vertex)
            while not q.is_empty():
                tmp: str = q.dequeue()
                for neighbour in self.adjacency_list[tmp]:
                    if self.color[neighbour] == self.color[tmp]:
                        return False
                    if self.color[neighbour] is None:
                        if self.color[tmp] == "red":
                            self.color[neighbour] = "blue"
                        else:
                            self.color[neighbour] = "red"
                        q.enqueue(neighbour)
        return True








================================================
FILE: graphs/is-graph-bipartite/main.py
================================================
from graph import Graph


g: Graph = Graph()
g.add_vertex("a")
g.add_vertex("b")
g.add_vertex("c")
g.add_vertex("d")

# a, b ||---|| c, d


g.add_edge("a", "c")
g.add_edge("a", "d")

g.add_edge("b", "c")
g.add_edge("b", "d")


# g.add_edge("a", "b")

print(g.bipartite_check())


================================================
FILE: graphs/is-graph-bipartite/queue.py
================================================
class Queue:
    def __init__(self):
        self._queue: list = []

    def is_empty(self) -> bool:
        return len(self._queue) == 0

    def enqueue(self, vertex: str):
        self._queue.append(vertex)

    def dequeue(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        return self._queue.pop(0)


================================================
FILE: graphs/kosarajus-algorithm/graph.py
================================================


from typing import Dict, List, Set

from stack import Stack

class Graph:


    def __init__(self) -> None:
        self.vertices:Set[str] = set()
        self.adjacency_list:Dict[str, Set[str]] = dict()
        self.adjacency_list_reversed:Dict[str, Set[str]] = dict()
        self.visited:Set[str] = set()
        self.stack:Stack = Stack()


    def add_vertex(self, label:str) -> None:
        self.vertices.add(label)
        self.adjacency_list[label] = set()
        self.adjacency_list_reversed[label] = set()


    def add_edge(self, label1:str, label2:str) -> None:
        if label1 not in self.vertices or label2 not in self.vertices:
            raise Exception('Vertices are not added')
        self.adjacency_list[label1].add(label2)
        self.adjacency_list_reversed[label2].add(label1)


    def kosaraju(self) -> List[List[str]]:
        for v in self.vertices:
            if v not in self.visited:
                self._dfs(v)

        self.visited.clear()

        connected_components:List[List[str]] = list()

        while not self.stack.is_empty():
            v:str = self.stack.pop()
            if v not in self.visited:
                connected:List[str] = list()
                self._dfs_reversed(v, connected)

                if len(connected) > 0:
                    connected_components.append(connected)

        return list(connected_components)



    def _dfs(self, label:str) -> None:
        self.visited.add(label)

        for n in self.adjacency_list[label]:
            if n not in self.visited:
                self._dfs(n)

        self.stack.push(label)


    def _dfs_reversed(self, v:str, connected:List[str]) -> None:
        connected.append(v)
        self.visited.add(v)
        for n in self.adjacency_list_reversed[v]:
            if n not in self.visited:
                self._dfs_reversed(n, connected)






================================================
FILE: graphs/kosarajus-algorithm/main.py
================================================
from graph import Graph

g: Graph = Graph()
for i in range(9):
    g.add_vertex(str(i))

g.add_edge('0', '1')
g.add_edge('1', '2')
g.add_edge('2', '3')
g.add_edge('3', '0')
g.add_edge('2', '4')
g.add_edge('4', '5')
g.add_edge('5', '6')
g.add_edge('6', '4')
g.add_edge('7', '6')
g.add_edge('8', '7')

print(g.kosaraju())


================================================
FILE: graphs/kosarajus-algorithm/stack.py
================================================
class Stack:
    def __init__(self):
        self._stack: list = []

    def is_empty(self) -> bool:
        return len(self._stack) == 0

    def push(self, item):
        self._stack.append(item)

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self._stack.pop()

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        # return self._stack[-1] # python way
        return self._stack[len(self._stack) - 1]

    def size(self) -> int:
        return len(self._stack)

================================================
FILE: graphs/minimum-spanning-tree/breadth-first-search/graph.py
================================================
from queue import Queue


class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacency_list: dict = {}
        self.prev: dict = {}
        self.distance: dict = {}
        self.colors: dict = {}

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacency_list[label]: list = []
        self.prev[label] = None
        self.distance[label] = 0
        self.colors[label] = "white"

    def add_edge(self, label1: str, label2: str):
        self.adjacency_list[label1].append(label2)
        self.adjacency_list[label2].append(label1)

    def minimum_spanning_tree(self, label: str) -> list:  # this is breadth first search
        min_edges: list = []
        q: Queue = Queue()
        q.enqueue(label)
        self.colors[label] = "gray"
        while not q.is_empty():
            tmp: str = q.dequeue()
            for neighbour in self.adjacency_list[tmp]:
                if self.colors[neighbour] == "white":
                    min_edges.append([tmp, neighbour])
                    self.prev[neighbour] = tmp
                    self.distance[neighbour] = self.distance[tmp] + 1
                    self.colors[neighbour] = "gray"
                    q.enqueue(neighbour)
            self.colors[tmp] = "black"
        return min_edges

    def return_path(self, label: str) -> str:
        if self.prev[label] is None:
            return label
        else:
            return self.return_path(self.prev[label]) + " -> " + label


================================================
FILE: graphs/minimum-spanning-tree/breadth-first-search/main.py
================================================
from graph import Graph

graph = Graph()

my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
# add vertices
for i in range(len(my_vertices)):
    graph.add_vertex(my_vertices[i])

graph.add_edge('A', 'B')
graph.add_edge('A', 'C')
graph.add_edge('A', 'D')
graph.add_edge('C', 'D')
graph.add_edge('C', 'G')
graph.add_edge('D', 'G')
graph.add_edge('D', 'H')
graph.add_edge('B', 'E')
graph.add_edge('B', 'F')
graph.add_edge('E', 'I')

edges: list = graph.minimum_spanning_tree("A")
print(edges)
print(graph.return_path("H"))


================================================
FILE: graphs/minimum-spanning-tree/breadth-first-search/queue.py
================================================
class Queue:
    def __init__(self):
        self.queue = []

    def enqueue(self, item):
        self.queue.insert(0, item)

    def dequeue(self):
        return self.queue.pop()

    def size(self):
        return len(self.queue)

    def is_empty(self):
        return self.queue == []

================================================
FILE: graphs/minimum-spanning-tree/kruskals-algorithm/graph.py
================================================

from typing import List, Set, Dict, Tuple

class Graph:


    def __init__(self) -> None:
        self.vertices:Set[str] = set()
        self.roots:Dict[str, str] = dict()
        self.sizes:Dict[str, int] = dict()
        self.edges:List[Tuple[str, str, int]] = list()
        self.mst:List[Tuple[str, str, int]] = list()

    
    def add_vertex(self, label:str) -> None:
        self.vertices.add(label)
        self.roots[label] = label
        self.sizes[label] = 1


    def add_edge(self, label1:str, label2:str, weight:int) -> None:
        if label1 not in self.vertices or label2 not in self.vertices:
            raise Exception("Vertices must be added before connecting them")
        self.edges.append((label1, label2, weight))


    def kruskal(self) -> List[Tuple[str, str, int]]:
        self.mst.clear()
        self.edges.sort(key = lambda edge: edge[2])

        for v1, v2, weight in self.edges:

            root1:str = self._find_root(v1)
            root2:str = self._find_root(v2)

            if root1 != root2:
                if self.sizes[root1] >= self.sizes[root2]:
                    self.roots[root2] = root1
                    self.sizes[root1] += self.sizes[root2]
                else:
                    self.roots[root1] = root2
                    self.sizes[root2] += self.sizes[root1]
                self.mst.append((v1, v2, weight))

        return list(self.mst)
    
    def _find_root(self, label:str) -> str:
        if self.roots[label] != label:
            self.roots[label] = self._find_root(self.roots[label])
        return self.roots[label]





================================================
FILE: graphs/minimum-spanning-tree/kruskals-algorithm/main.py
================================================
from graph import Graph

g: Graph = Graph()

g.add_vertex('a')
g.add_vertex('b')
g.add_vertex('d')
g.add_vertex('c')
g.add_vertex('e')
g.add_vertex('f')

g.add_edge('a', 'f', 4)
g.add_edge('a', 'b', 23)

g.add_edge('b', 'd', 17)
g.add_edge('b', 'c', 3)
g.add_edge('c', 'd', 41)

g.add_edge('c', 'f', 2)
g.add_edge('f', 'e', 1)

print(g.kruskal())


================================================
FILE: graphs/minimum-spanning-tree/prims-algorithm/graph.py
================================================

from typing import Dict, List

from vertex import Vertex
from priorityqueue import PriorityQueue

class Graph:

    def __init__(self) -> None:
        self.vertices: Dict[str, Vertex] = dict()
        self.adjacency_map: Dict[str, List[Vertex]] = dict()
        self.prev:Dict[str, str] = dict()


    def add_vertex(self, label:str, weight:int=float('inf')):
        self.vertices[label] = Vertex(label)
        self.adjacency_map[label] = []
        self.prev[label] = None


    def add_edge(self, label1:str, label2:str, weight:int):
        self.adjacency_map[label1].append(Vertex(label2, weight))
        self.adjacency_map[label2].append(Vertex(label1, weight))

    
    def prims(self, label:str):
        res:str = ''
        v:Vertex = self.vertices[label]
        v.weight = 0

        pq:PriorityQueue = PriorityQueue()
        for k in self.vertices:
            vertex = self.vertices[k]
            pq.insert(vertex)

        while not pq.is_empty():
            v = pq.delete_min()
            print('Min is ', v.label)
            if self.prev[v.label] is not None:
                res += f'{self.prev[v.label]} -> {v.label}, '
            for neighbour in self.adjacency_map[v.label]:
                vertex:Vertex = self.vertices[neighbour.label]
                if neighbour.weight < vertex.weight:
                    vertex.weight = neighbour.weight
                    self.prev[vertex.label] = v.label
                    pq.decrease_key(vertex.index)

        print(res)


================================================
FILE: graphs/minimum-spanning-tree/prims-algorithm/main.py
================================================
from graph import Graph


graph: Graph = Graph()

graph.add_vertex("a")
graph.add_vertex("b")
graph.add_vertex("f")
graph.add_vertex("c")
graph.add_vertex("d")
graph.add_vertex("e")

graph.add_edge("a", "b", 4)
graph.add_edge("a", "f", 2)
graph.add_edge("b", "c", 6)
graph.add_edge("f", "b", 3)
graph.add_edge("f", "c", 1)
graph.add_edge("f", "e", 4)
graph.add_edge("c", "d", 3)
graph.add_edge("d", "e", 2)
graph.prims("a")  # a -> f, f -> c, f -> b, c -> d, d -> e,


================================================
FILE: graphs/minimum-spanning-tree/prims-algorithm/priorityqueue.py
================================================
from typing import List

from vertex import Vertex


class PriorityQueue:


    def __init__(self) -> None:
        self.queue: List[Vertex] = [None]
        self.pointer:int = 0


    def is_empty(self) -> bool:
        return self.pointer == 0


    def insert(self, v:Vertex):
        self.queue.append(v)
        self.pointer += 1
        v.index = self.pointer
        self.perc_up(self.pointer)


    def perc_up(self, index:int):
        while index // 2 > 0:
            if self.queue[index].weight < self.queue[index // 2].weight:
                self.queue[index], self.queue[index // 2] = self.queue[index // 2], self.queue[index]
                self.queue[index].index = index 
                self.queue[index // 2].index = index // 2
            index = index // 2


    def decrease_key(self, key:int):
        self.perc_up(key)


    def get_min(self) -> Vertex:
        if self.is_empty():
            raise Exception('Priority queue is empty')
        return self.queue[1]


    def delete_min(self) -> Vertex:
        if self.is_empty():
            raise Exception('Priority queue is empty')
        v:Vertex = self.queue[1]
        self.queue[1] = self.queue[self.pointer]
        self.queue[1].index = 1
        self.queue.pop()
        self.pointer -= 1
        self.perc_down(1)
        return v


    def perc_down(self, index:int):
        while index * 2 <= self.pointer:
            min_index:int = self.find_min_index(index)
            if self.queue[index].weight > self.queue[min_index].weight:
                self.queue[index], self.queue[min_index] = self.queue[min_index], self.queue[index]
                self.queue[min_index].index = min_index 
                self.queue[index].index = index
            index = min_index



    def find_min_index(self, index:int) -> int:
        if index * 2 + 1 > self.pointer:
            return index * 2
        else:
            if self.queue[index * 2].weight <= self.queue[index * 2 + 1].weight:
                return index * 2
            else:
                return index * 2 + 1



================================================
FILE: graphs/minimum-spanning-tree/prims-algorithm/vertex.py
================================================
class Vertex:

    def __init__(self, label:str=None, weight:int=float('inf'), index:int=None) -> None:
        self.label:str = label
        self.weight:int = weight
        self.index:int = index



================================================
FILE: graphs/topological-sorting/graph.py
================================================
class Graph:
    def __init__(self):
        self.vertices: list = []
        self.adjacencyList: dict = {}
        self.colors: dict = {}
        self.previous: dict = {}
        self.distance: dict = {}
        self.time: int = 0
        self.entry: dict = {}
        self.exit: dict = {}
        self.no_incoming_edges: dict = {} # to avoid looking through whole list
        self.explored: list = []

    def add_vertex(self, label: str):
        self.vertices.append(label)
        self.adjacencyList[label]: list = []
        self.colors[label] = "white"
        self.distance[label] = 0
        self.previous[label] = None
        self.no_incoming_edges[label] = label

    def add_edge(self, label1: str, label2: str):
        self.adjacencyList[label1].append(label2)
        if label2 in self.no_incoming_edges:
            del self.no_incoming_edges[label2]  # remove vertex, it has incoming edge

    def topsort(self):
        # perform depth first search on vertices with no incoming edges
        for label in self.no_incoming_edges:
            self.dfs(label)
        self.explored.reverse()
        print(self.explored)

    def dfs(self, start: str):
        self.time += 1
        self.entry[start] = self.time
        self.colors[start] = "gray"
        for neighbour in self.adjacencyList[start]:
            if self.colors[neighbour] == "white":
                self.previous[neighbour] = start
                self.distance[neighbour] = self.distance[neighbour] + 1
                self.dfs(neighbour)
        self.time += 1
        self.exit[start] = self.time
        self.colors[start] = "black"
        self.explored.append(start)

    def show_path(self, label: str) -> str:
        if self.previous[label] is None:
            return label
        else:
            return self.show_path(self.previous[label]) + " -> " + label



================================================
FILE: graphs/topological-sorting/main.py
================================================
from graph import Graph

graph = Graph()

vertices = ["a", "b", "c", "d", "e", "f"]
for vertex in vertices:
    graph.add_vertex(vertex)


graph.add_edge("a", "c")
graph.add_edge("c", "d")
graph.add_edge("c", "f")
graph.add_edge("b", "c")
graph.add_edge("b", "e")

graph.topsort()



================================================
FILE: graphs/union-find/number-of-connected-components/graph.py
================================================
from typing import List

def find_parent(i: int, components: List[int]) -> int:
    
    while i != components[i]:
        i = components[i]
    return i


class Graph:
    def number_of_connected_components(self, edges_matrix: List[List[int]]) -> int:
        
        n: int = len(edges_matrix)
            
        number_of_components: int = n
            
        components: List[int] = [None] * n
            
        for i in range(n):
            components[i] = i
            
        for i in range(n):
            
            for j in range(n):
                
                if edges_matrix[i][j] == 1:
                    
                    parent_one: int = find_parent(i, components)
                    parent_two: int = find_parent(j, components)
                        
                    if parent_one != parent_two:
                        components[parent_one] = parent_two
                        number_of_components -= 1
                        
        return number_of_components

================================================
FILE: graphs/union-find/union-find-path-compression/graph.py
================================================
from vertex import Vertex


class Graph:

    def __init__(self):
        self.vertices: dict = {}
        self.edges: list = []

    def add_vertex(self, label: str = None):
        self.vertices[label] = Vertex(label)

    def add_edge(self, label1: str, label2: str):
        self.edges.append([label1, label2])

    def union_find(self):

        for edge in self.edges:
            label_one: str = edge[0]
            label_two: str = edge[1]
            vertex_one = self.vertices[label_one]
            vertex_two = self.vertices[label_two]

            root_one: Vertex = self.find_root(vertex_one)
            root_two: Vertex = self.find_root(vertex_two)

            if root_one != root_two:
                if root_one.rank > root_two.rank:
                    root_two.parent = root_one
                    root_one.rank = root_one.rank + 1
                else:
                    root_one.parent = root_two
                    root_two.rank = root_two.rank + 1

    def find_root(self, vertex: Vertex):
        if vertex.parent != vertex:
            vertex.parent = self.find_root(vertex.parent)
            return vertex.parent
        return vertex.parent


================================================
FILE: graphs/union-find/union-find-path-compression/main.py
================================================
from graph import Graph
from vertex import Vertex

g: Graph = Graph()
g.add_vertex('a')
g.add_vertex('b')
g.add_vertex('c')
g.add_vertex('d')
g.add_vertex('e')
g.add_vertex('f')

g.add_edge('a', 'b')
g.add_edge('c', 'd')
g.add_edge('d', 'e')
g.add_edge('c', 'f')
g.add_edge('f', 'b')

g.union_find()

for label in g.vertices:
    vertex: Vertex = g.vertices[label]
    print(f'{vertex.label} parent is {vertex.parent.label}, rank is {vertex.parent.rank}')






================================================
FILE: graphs/union-find/union-find-path-compression/vertex.py
================================================
class Vertex:

    def __init__(self, label: str = None):
        self.label = label
        self.parent: 'Vertex' = self
        self.rank: int = 0



================================================
FILE: hash-table/chaining.py
================================================


from typing import Any, List

class KeyValue:

    def __init__(self, key: int, value: Any) -> None:
        self.key: int = key
        self.value: Any = value


class HashTable:


    def __init__(self, capacity:int = 11) -> None:
        self.capacity: int = capacity
        self.length: int = 0
        self.table: List[List[KeyValue]] = [None] * self.capacity

    
    def put(self, key: int, value: Any) -> int:
        index: int = self.hash(key)
        if self.table[index] is None:
            self.table[index] = [KeyValue(key, value)]
            self.length += 1
        else:
            found: bool = False
            i: int = 0
            items: List[KeyValue] = self.table[index]
            while i < len(items) and not found:
                if items[i].key == key:
                    found = True
                else:
                    i += 1
            if found:
                items[i].value = value
            else:
                items.append(KeyValue(key, value))
                self.length += 1


    def get(self, key: int) -> Any:
        index: int = self.hash(key)
        if self.table[index] is None:
            return None
        else:
            found: bool = False
            i: int = 0
            items: List[KeyValue] = self.table[index]
            while i < len(items) and not found:
                if items[i].key == key:
                    found = True
                else:
                    i += 1
            if found:
                return items[i].value
            else:
                return None


    def contains(self, key: int) -> bool:
        index: int = self.hash(key)
        if self.table[index] is None:
            return False
        else:
            found: bool = False
            i: int = 0
            items: List[KeyValue] = self.table[index]
            while i < len(items) and not found:
                if items[i].key == key:
                    found = True
                else:
                    i += 1
            if found:
                return True
            else:
                return False


    def delete(self, key: int) -> None:
        index: int = self.hash(key)
        if self.table[index] is None:
            return None
        else:
            found: bool = False
            i: int = 0
            items: List[KeyValue] = self.table[index]
            while i < len(items) and not found:
                if items[i].key == key:
                    found = True
                else:
                    i += 1
            if not found:
                return None
            
            items.pop(i)
            if len(items) == 0:
                self.table[index] = None
            return None


    def hash(self, key: int) -> int:
        return key % self.capacity


    def size(self) -> int:
        return self.length



ht: HashTable = HashTable()
ht.put(11, 'string 11')
ht.put(22, 'string 22')
ht.put(33, 'string 33')
ht.put(44, 'string 44')

ht.put(21, 'string 21')
ht.put(12, 'string 12')

print(ht.size())
print('Get 11', ht.get(11))
print('Get 33', ht.get(33))
print('Get 147', ht.get(147))
print('----------------------------------------')

print('Contains 22', ht.contains(22))
ht.delete(22)
print(ht.size())
print('Contains 22', ht.contains(22))
print('----------------------------------------')

print('Contains 77', ht.contains(77))
ht.put(44, 'string 144')
ht.put(77, 'string 77')

print(ht.size())
print('Contains 77', ht.contains(77))


================================================
FILE: hash-table/linear-probing.py
================================================


from typing import Any, List, Tuple


class HashTable:

    def __init__(self, capacity:int = 11) -> None:
        self.capacity:int  = capacity
        self.keys: List[int] = [None] * self.capacity
        self.values: List[int] = [None] * self.capacity
        self.length: int = 0


    def put(self, key:int, value:Any):
        index, contains = self.find(key)
        if contains:
            self.values[index] = value
            return
        hash:int = self.hash(key)
        if self.keys[hash] == float('inf') or self.keys[hash] is None:
            self.keys[hash] = key 
            self.values[hash] = value
            self.length += 1
        else:
            new_hash:int = self.rehash(hash) % self.capacity
            while self.keys[new_hash] is not None and self.keys[new_hash] != float('inf') and new_hash != hash:
                new_hash = self.rehash(new_hash)
            if self.keys[new_hash] == float('inf') or self.keys[new_hash] is None:
                self.keys[new_hash] = key 
                self.values[new_hash] = value
                self.length += 1

        
    def contains(self, key:int) -> bool:
        _, contains = self.find(key)
        return contains


    def get(self, key:int) -> Any:
        index, contains = self.find(key)
        if contains:
            return self.values[index] 
        return None


    def delete(self, key:int):
        index, contains = self.find(key)
        if not contains:
            return
        self.keys[index] = float('inf')
        self.values[index] = None
        self.length -= 1


    def find(self, key:int) -> Tuple[int, bool]:
        hash:int = self.hash(key)
        if self.keys[hash] == key:
            return (hash, True)
        elif self.keys[hash] is None:
            return (None, False)
        else:
            new_hash:int = self.rehash(hash) % self.capacity
            while self.keys[new_hash] != key and self.keys[new_hash] is not None and new_hash != hash:
                new_hash = self.rehash(new_hash)

            if self.keys[new_hash] == key:
                return (new_hash, True)
            return (None, False)


    def size(self) -> int:
        return self.length

    def hash(self, key:int) -> int:
        return key % self.capacity


    def rehash(self, old_hash:int) -> int:
        return (old_hash + 1) % self.capacity


ht: HashTable = HashTable()
ht.put(11, 'string 11')
ht.put(22, 'string 22')
ht.put(33, 'string 33')
ht.put(44, 'string 44')

ht.put(21, 'string 21')
ht.put(12, 'string 12')

print(ht.keys)
print(ht.values)
print(ht.size())
print('Get 11', ht.get(11))
print('Get 22', ht.get(22))
print('Get 147', ht.get(147))
print('----------------------------------------')

print('Contains 22', ht.contains(22))
ht.delete(22)
print(ht.size())
print(ht.keys)
print(ht.values)
print('Contains 22', ht.contains(22))
print('----------------------------------------')

print('Contains 44', ht.contains(44))
print(ht.keys)
print(ht.values)
print('Contains 77', ht.contains(77))
ht.put(44, 'string 144')
ht.put(77, 'string 77')

print(ht.size())
print(ht.keys)
print(ht.values)
print('Contains 77', ht.contains(77))
print('Contains 44', ht.contains(44))



================================================
FILE: linked-lists/circular-doubly-linked-list/list.py
================================================
from node import Node


class List:
    def __init__(self):
        self.head: Node = None
        self.tail: Node = None

    def is_empty(self) -> bool:
        return self.head is None

    def print_all(self):
        if self.is_empty():
            return
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            print(tmp.key, end=", ")
            tmp = tmp.next
            if tmp == self.head:
                stopped = True
        print("")

    def number_of_elements(self) -> int:
        if self.is_empty():
            return 0
        count: int = 0
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            count += 1
            tmp = tmp.next
            if tmp == self.head:
                stopped = True
        return count

    def add_to_head(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
            self.head.prev = self.tail
            self.tail.next = self.head
        else:
            self.head = Node(key, self.tail, self.head)
            self.head.next.prev = self.head
            self.tail.next = self.head

    def add_to_tail(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
            self.head.prev = self.tail
            self.tail.next = self.head
        else:
            self.tail.next = Node(key, self.tail, self.head)
            self.tail = self.tail.next
            self.head.prev = self.tail

    def delete_from_head(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.head
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                self.head = self.head.next
                self.head.prev = self.tail
                self.tail.next = self.head
            return ret_node.key

    def delete_from_tail(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.tail
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                self.tail = self.tail.prev
                self.tail.next = self.head
                self.head.prev = self.tail
            return ret_node.key

    def delete_nodes_with_value(self, key: int):
        if self.is_empty():
            return None
        ret_value: int = None
        tmp: Node = self.head
        while tmp.next != self.head:
            if tmp.next.key == key:
                ret_value = tmp.next.key
                tmp.next = tmp.next.next
                tmp.next.prev = tmp
            else:
                tmp = tmp.next
        self.tail = tmp
        if self.head.key == key:
            ret_value = self.delete_from_head()
        return ret_value

    def delete_on_index(self, index: int):
        if self.is_empty():
            return
        end_index: int = self.number_of_elements() - 1
        if index < 0 or index > end_index:
            return
        if index == 0:
            self.delete_from_head()
        elif index == end_index:
            self.delete_from_tail()
        else:
            tmp: Node = self.head
            count: int = 0
            while count < index:
                tmp = tmp.next
                count += 1
            tmp.prev.next = tmp.next
            tmp.next.prev = tmp.prev

    def insert_after(self, list_element: int, new_element: int):
        if self.is_empty():
            return
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            if tmp.key == list_element:
                if tmp == self.tail:
                    self.add_to_tail(new_element)
                else:
                    new_node = Node(new_element, tmp, tmp.next)
                    tmp.next = new_node
                    new_node.next.prev = new_node
                tmp = tmp.next
            tmp = tmp.next
            if tmp == self.head:
                stopped = True

    def insert_before(self, list_element: int, new_element: int):
        if self.is_empty():
            return
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            if tmp.key == list_element:
                if tmp == self.head:
                    self.add_to_head(new_element)
                else:
                    new_node = Node(new_element, tmp.prev, tmp)
                    new_node.prev.next = new_node
                    tmp.prev = new_node
            tmp = tmp.next
            if tmp == self.head:
                stopped = True

    def sort(self):
        swapped: bool = True
        outer: Node = self.head
        inner: Node = self.tail
        while outer != self.tail:
            inner = self.tail
            while inner != outer:
                if inner.key < inner.prev.key:
                    k = inner.key
                    inner.key = inner.prev.key
                    inner.prev.key = k
                inner = inner.prev
            outer = outer.next




================================================
FILE: linked-lists/circular-doubly-linked-list/node.py
================================================
class Node:
    def __init__(self, key: int=None, prev=None, next=None):
        self.key = key
        self.prev = prev
        self.next = next



================================================
FILE: linked-lists/circular-singly-linked-list/list.py
================================================
from node import Node


class List:
    def __init__(self):
        self.head: Node = None
        self.tail: Node = None

    def is_empty(self) -> bool:
        return self.head is None

    def print_all(self):
        if self.is_empty():
            return
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            print(tmp.key, end=", ")
            tmp = tmp.next
            if tmp == self.head:
                stopped = True
        print("")

    def number_of_elements(self) -> int:
        if self.is_empty():
            return 0
        count: int = 0
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            count += 1
            tmp = tmp.next
            if tmp == self.head:
                stopped = True
        return count

    def add_to_head(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
            self.tail.next = self.head
        else:
            self.head = Node(key, self.head)
            self.tail.next = self.head

    def add_to_tail(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
            self.tail.next = self.head
        else:
            self.tail.next = Node(key, self.head)
            self.tail = self.tail.next

    def delete_from_head(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.head
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                self.head = self.head.next
                self.tail.next = self.head
            return ret_node.key

    def delete_from_tail(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.tail
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                tmp: Node = self.head
                while tmp.next != self.tail:
                    tmp = tmp.next
                self.tail = tmp
                tmp.next = self.head
            return ret_node.key

    def delete_nodes_with_value(self, key: int):
        if self.is_empty():
            return None
        ret_value: int = None
        tmp: Node = self.head
        while tmp.next != self.head:
            if tmp.next.key == key:
                ret_value = tmp.next.key
                tmp.next = tmp.next.next
            else:
                tmp = tmp.next
        self.tail = tmp
        if self.head.key == key:
            ret_value = self.delete_from_head()
        return ret_value

    def delete_on_index(self, index: int):
        if self.is_empty():
            return
        end_index: int = self.number_of_elements() - 1
        if index < 0 or index > end_index:
            return
        if index == 0:
            self.delete_from_head()
        elif index == end_index:
            self.delete_from_tail()
        else:
            prev: Node = None
            tmp: Node = self.head
            count: int = 0
            while count < index:
                prev = tmp
                tmp = tmp.next
                count += 1
            prev.next = tmp.next

    def insert_after(self, list_element: int, new_element: int):
        if self.is_empty():
            return
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            if tmp.key == list_element:
                if tmp == self.tail:
                    self.add_to_tail(new_element)
                else:
                    tmp.next = Node(new_element, tmp.next)
                tmp = tmp.next
            tmp = tmp.next
            if tmp == self.head:
                stopped = True

    def insert_before(self, list_element: int, new_element: int):
        if self.is_empty():
            return
        prev: Node = None
        tmp: Node = self.head
        stopped: bool = False
        while not stopped:
            if tmp.key == list_element:
                if tmp == self.head:
                    self.add_to_head(new_element)
                else:
                    prev.next = Node(new_element, tmp)
            prev = tmp
            tmp = tmp.next
            if tmp == self.head:
                stopped = True

    def sort(self):
        swapped: bool = True
        while swapped:
            swapped = False
            tmp: Node = self.head
            while tmp != self.tail:
                if tmp.key > tmp.next.key:
                    k = tmp.key
                    tmp.key = tmp.next.key
                    tmp.next.key = k
                    swapped = True
                tmp = tmp.next




================================================
FILE: linked-lists/circular-singly-linked-list/node.py
================================================
class Node:
    def __init__(self, key: int=None, next=None):
        self.key = key
        self.next = next



================================================
FILE: linked-lists/doubly-linked-list/list.py
================================================
from node import Node


class List:
    def __init__(self):
        self.head: Node = None
        self.tail: Node = None

    def is_empty(self) -> bool:
        return self.head is None

    def print_all(self):
        tmp: Node = self.head
        while tmp is not None:
            print(tmp.key, end=", ")
            tmp = tmp.next
        print("")

    def number_of_elements(self) -> int:
        count: int = 0
        tmp: Node = self.head
        while tmp is not None:
            count += 1
            tmp = tmp.next
        return count

    def add_to_head(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
        else:
            self.head = Node(key, None, self.head)
            self.head.next.prev = self.head

    def add_to_tail(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
        else:
            self.tail.next = Node(key, self.tail)
            self.tail = self.tail.next

    def delete_from_head(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.head
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                self.head = self.head.next
                self.head.prev = None
            return ret_node.key

    def delete_from_tail(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.tail
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                self.tail = self.tail.prev;
                self.tail.next = None
            return ret_node.key

    def delete_nodes_with_value(self, key: int):
        if self.is_empty():
            return None
        ret_value: int = None
        tmp: Node = self.head
        while tmp.next is not None:
            if tmp.next.key == key:
                ret_value = tmp.next.key
                tmp.next = tmp.next.next
                if tmp.next is not None:
                    tmp.next.prev = tmp
            else:
                tmp = tmp.next
        self.tail = tmp
        if self.head.key == key:
            ret_value = self.delete_from_head()
        return ret_value

    def delete_on_index(self, index: int):
        if self.is_empty():
            return
        end_index: int = self.number_of_elements() - 1
        if index < 0 or index > end_index:
            return
        if index == 0:
            self.delete_from_head()
        elif index == end_index:
            self.delete_from_tail()
        else:
            tmp: Node = self.head
            count: int = 0
            while count < index:
                tmp = tmp.next
                count += 1
            tmp.prev.next = tmp.next
            tmp.next.prev = tmp.prev

    def insert_after(self, list_element: int, new_element: int):
        tmp: Node = self.head
        while tmp is not None:
            if tmp.key == list_element:
                if tmp == self.tail:
                    self.add_to_tail(new_element)
                else:
                    new_node: Node = Node(new_element, tmp, tmp.next)
                    tmp.next = new_node
                    new_node.next.prev = new_node
                tmp = tmp.next
            tmp = tmp.next

    def insert_before(self, list_element: int, new_element: int):
        tmp: Node = self.head
        while tmp is not None:
            if tmp.key == list_element:
                if tmp == self.head:
                    self.add_to_head(new_element)
                else:
                    new_node: Node = Node(new_element, tmp.prev, tmp)
                    new_node.prev.next = new_node
                    tmp.prev = new_node
            tmp = tmp.next

    def sort(self):
        swapped: bool = True
        outer: Node = self.head
        inner: Node = self.tail
        while outer != self.tail:
            inner = self.tail
            while inner != outer:
                if inner.key < inner.prev.key:
                    k = inner.key
                    inner.key = inner.prev.key
                    inner.prev.key = k
                inner = inner.prev
            outer = outer.next




================================================
FILE: linked-lists/doubly-linked-list/node.py
================================================
class Node:
    def __init__(self, key: int=None, prev=None, next=None):
        self.key = key
        self.prev = prev
        self.next = next



================================================
FILE: linked-lists/singly-linked-list/list.py
================================================
from node import Node


class List:
    def __init__(self):
        self.head: Node = None
        self.tail: Node = None

    def is_empty(self) -> bool:
        return self.head is None

    def print_all(self):
        tmp: Node = self.head
        while tmp is not None:
            print(tmp.key, end=", ")
            tmp = tmp.next
        print("")

    def number_of_elements(self) -> int:
        count: int = 0
        tmp: Node = self.head
        while tmp is not None:
            count += 1
            tmp = tmp.next
        return count

    def add_to_head(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
        else:
            self.head = Node(key, self.head)

    def add_to_tail(self, key: int):
        if self.is_empty():
            self.head = self.tail = Node(key)
        else:
            self.tail.next = Node(key)
            self.tail = self.tail.next

    def delete_from_head(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.head
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                self.head = self.head.next
            return ret_node.key

    def delete_from_tail(self) -> int:
        if self.is_empty():
            return None
        else:
            ret_node: Node = self.tail
            if self.head == self.tail:
                self.head = self.tail = None
            else:
                tmp: Node = self.head
                while tmp.next != self.tail:
                    tmp = tmp.next
                tmp.next = None
                self.tail = tmp
            return ret_node.key

    def delete_nodes_with_value(self, key: int):
        if self.is_empty():
            return None
        ret_value: int = None
        tmp: Node = self.head
        while tmp.next is not None:
            if tmp.next.key == key:
                ret_value = tmp.next.key
                tmp.next = tmp.next.next
            else:
                tmp = tmp.next
        self.tail = tmp
        if self.head.key == key:
            ret_value = self.delete_from_head()
        return ret_value

    def delete_on_index(self, index: int):
        if self.is_empty():
            return
        end_index: int = self.number_of_elements() - 1
        if index < 0 or index > end_index:
            return
        if index == 0:
            self.delete_from_head()
        elif index == end_index:
            self.delete_from_tail()
        else:
            prev: Node = None
            tmp: Node = self.head
            count: int = 0
            while count < index:
                prev = tmp
                tmp = tmp.next
                count += 1
            prev.next = tmp.next

    def insert_after(self, list_element: int, new_element: int):
        tmp: Node = self.head
        while tmp is not None:
            if tmp.key == list_element:
                if tmp == self.tail:
                    self.add_to_tail(new_element)
                else:
                    tmp.next = Node(new_element, tmp.next)
                tmp = tmp.next
            tmp = tmp.next

    def insert_before(self, list_element: int, new_element: int):
        prev: Node = None
        tmp: Node = self.head
        while tmp is not None:
            if tmp.key == list_element:
                if tmp == self.head:
                    self.add_to_head(new_element)
                else:
                    prev.next = Node(new_element, tmp)
            prev = tmp
            tmp = tmp.next

    def sort(self):
        swapped: bool = True
        while swapped:
            swapped = False
            tmp: Node = self.head
            while tmp != self.tail:
                if tmp.key > tmp.next.key:
                    k = tmp.key
                    tmp.key = tmp.next.key
                    tmp.next.key = k
                    swapped = True
                tmp = tmp.next



================================================
FILE: linked-lists/singly-linked-list/node.py
================================================
class Node:
    def __init__(self, key: int=None, next=None):
        self.key = key
        self.next = next



================================================
FILE: queue/circular-queue-fixed-size-array-impl.py
================================================
class Queue:
    def __init__(self, length: int = 10):
        self._length: int = length
        self._queue: list = [None] * self._length
        self._front = self._rear = -1
        self._count: int = 0

    def enqueue(self, item):
        if self.is_full():
            raise Exception("Queue is full")
        self._rear = (self._rear + 1) % self._length
        if self._front == -1:
            self._front += 1
        self._count += 1
        self._queue[self._rear] = item

    def dequeue(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        ret_value = self._queue[self._front]
        if self._front == self._rear:
            self._front = self._rear = -1
        else:
            self._front = (self._front + 1) % self._length
        self._count -= 1
        return ret_value

    def peek(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        return self._queue[self._front]

    def size(self) -> int:
        return self._count

    def is_empty(self) -> bool:
        return self._front == -1

    def is_full(self) -> bool:
        return (self._rear + 1) % self._length == self._front


def hot_potato(namelist, number):
    q = Queue(30)

    for name in namelist:
        q.enqueue(name)

    while q.size() > 1:
        for i in range(number):
            q.enqueue(q.dequeue())
        q.dequeue()

    return q.dequeue()


print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7))



================================================
FILE: queue/queue-array-impl.py
================================================
class Queue:
    def __init__(self):
        self._queue = []

    def enqueue(self, item):
        self._queue.insert(0, item)

    def dequeue(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        return self._queue.pop()

    def peek(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        return self._queue[len(self._queue) - 1]

    def size(self) -> int:
        return len(self._queue)

    def is_empty(self) -> bool:
        return len(self._queue) == 0


def hot_potato(namelist, number):
    q = Queue()

    for name in namelist:
        q.enqueue(name)

    while q.size() > 1:
        for i in range(number):
            q.enqueue(q.dequeue())
        q.dequeue()

    return q.dequeue()


print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7))


================================================
FILE: queue/queue-fixed-size-array-impl.py
================================================
class Queue:
    def __init__(self, length: int = 10):
        self._length: int = length
        self._queue: list = [None] * self._length
        self._front = self._rear = -1
        self._count: int = 0

    def enqueue(self, item):
        if self.is_full():
            raise Exception("Queue is full")
        self._rear += 1
        if self._front == -1:
            self._front += 1
        self._count += 1
        self._queue[self._rear] = item

    def dequeue(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        ret_value = self._queue[self._front]
        if self._front == self._rear:
            self._front = self._rear = -1
        else:
            self._front += 1
        self._count -= 1
        return ret_value

    def peek(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        return self._queue[self._front]

    def size(self) -> int:
        return self._count

    def is_empty(self) -> bool:
        return self._front == -1

    def is_full(self) -> bool:
        return self._rear == self._length - 1


def hot_potato(namelist, number):
    q = Queue(300)

    for name in namelist:
        q.enqueue(name)

    while q.size() > 1:
        for i in range(number):
            q.enqueue(q.dequeue())
        q.dequeue()

    return q.dequeue()


print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7))



================================================
FILE: queue/queue-linked-list-impl.py
================================================

class Node:
    def __init__(self, key, next=None):
        self.key = key
        self.next = next


class Queue:
    def __init__(self):
        self._head = self._tail = None
        self._count: int = 0

    def enqueue(self, item):
        if self.is_empty():
            self._head = self._tail = Node(item)
        else:
            self._tail.next = Node(item)
            self._tail = self._tail.next
        self._count += 1

    def dequeue(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        ret_value: Node = self._head
        if self._head == self._tail:
            self._head = self._tail = None
        else:
            self._head = self._head.next
        self._count -= 1
        return ret_value.key

    def peek(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        return self._head.key

    def size(self) -> int:
        return self._count

    def is_empty(self) -> bool:
        return self._head is None


def hot_potato(namelist, number):
    q: Queue = Queue()
    for person in namelist:
        q.enqueue(person)
    while q.size() > 1:
        for i in range(number):
            q.enqueue(q.dequeue())
        q.dequeue()
    return q.dequeue()


print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7))



================================================
FILE: queue/queue-two-stacks-impl.py
================================================
class Stack:
    def __init__(self):
        self.items = []

    def is_empty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items) - 1]

    def size(self):
        return len(self.items)


class Queue:
    def __init__(self):
        self.input_stack: Stack = Stack()
        self.output_stack: Stack = Stack()

    def is_empty(self) -> bool:
        return self.input_stack.is_empty()

    def size(self) -> int:
        return self.input_stack.size()

    def enqueue(self, item):
        self.input_stack.push(item)

    def dequeue(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        while not self.input_stack.is_empty():
            self.output_stack.push(self.input_stack.pop())
        ret_value = self.output_stack.pop()
        while not self.output_stack.is_empty():
            self.input_stack.push(self.output_stack.pop())
        return ret_value

    def peek(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        while not self.input_stack.is_empty():
            self.output_stack.push(self.input_stack.pop())
        ret_value = self.input_stack.pop()
        self.output_stack.push(ret_value)
        while not self.output_stack.is_empty():
            self.input_stack.push(self.output_stack.pop())
        return ret_value


def hot_potato(people: list, num: int) -> str:
    q: Queue = Queue()
    for person in people:
        q.enqueue(person)

    while q.size() > 1:
        for i in range(num):
            q.enqueue(q.dequeue())
        q.dequeue()

    return q.dequeue()


print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7))


================================================
FILE: recursion/convert-number-iterative.py
================================================
from stack import Stack


def converter(num: int, base: int) -> str:
    digits = "0123456789ABCDEF"
    stack: Stack = Stack()
    while num > 0:
        stack.push(digits[num % base])
        num = num // base
    result: str = ""
    while not stack.is_empty():
        result += stack.pop()
    return result


'''
digits = "0123456789ABCDEF"
def converter(num: int, base: int) -> str:

    r:str = ''
    while num >  0:
        r = digits[num % base] + r
        num = num // base       
    
    return r
'''

print(converter(1453, 16)) # 5AD


================================================
FILE: recursion/convert-number.py
================================================
def converter(num: int, base: int) -> str:
    digits = "0123456789ABCDEF"
    if num < base:
        return digits[num]
    else:
        return converter(num // base, base) + digits[num % base]


print(converter(1453, 16)) # 5AD

================================================
FILE: recursion/factorial.py
================================================
def factorial_rec(n: int) -> int:
    if n <= 1:
        return 1
    else:
        return n * factorial_rec(n - 1)


def factorial_it(n: int) -> int:
    if n <= 1:
        return 1
    result: int = 1
    while n > 1:
        result *= n
        n -= 1
    return result


================================================
FILE: recursion/fibonacci-iterative.py
================================================
def fibonacci(n: int) -> int:
    if n <= 1:
        return 0
    elif n == 2:
        return 1
    prev: int = 0
    curr: int = 1
    for _ in range(n - 2):
        prev, curr = curr, prev + curr
    return curr


for i in range(6):
    print(fibonacci(i))


================================================
FILE: recursion/fibonacci-memoization.py
================================================

nums: dict = {}


def fibonacci(n: int) -> int:
    if n <= 1:
        return 0
    elif n == 2:
        return 1
    else:
        if n in nums:
            return nums[n]
        nums[n] = fibonacci(n - 1) + fibonacci(n - 2)
        return nums[n]


for i in range(6):
    print(fibonacci(i))


================================================
FILE: recursion/fibonacci-recursive-worst-solution.py
================================================
def fibonacci(n: int) -> int:
    if n <= 1:
        return 0
    elif n == 2:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)


for i in range(6):
    print(fibonacci(i))


================================================
FILE: recursion/fibonacci-recursive.py
================================================
def fibonacci(n: int) -> int:

    def fibonacci_helper(num: int, prev: int = 0, curr: int = 1) -> int:
        if num <= 1:
            return prev
        elif num == 2:
            return curr
        else:
            return fibonacci_helper(num - 1, curr, prev + curr)

    return fibonacci_helper(n)


for i in range(6):
    print(fibonacci(i))


================================================
FILE: recursion/fibonacci-sum-iterative.py
================================================
def fibonaci(n: int):
    
    if n == 1:
        return 0
    if n == 2:
        return 1

    prev: int = 0
    curr: int = 1
    acc: int = 1

    for _ in range(n - 2):
        prev,curr = curr, prev + curr
        acc += curr


    return acc


for i in range(6):
    print(f'Fibonacci {i} is : {fibonacci(i)}')


================================================
FILE: recursion/fibonacci-sum-recursive.py
================================================
def fibonacci(n: int) -> int:

    def fibonacci_helper(num: int, acc: int = 0, prev: int = 0, curr: int = 1):
        if num <= 1:
            return prev
        elif num == 2:
            return acc + curr
        else:
            return fibonacci_helper(num - 1, acc + curr, curr, prev + curr)

    return fibonacci_helper(n)


for i in range(6):
    print(fibonacci(i))


================================================
FILE: recursion/maze.py
================================================

from typing import List, Any

class Stack:

    def __init__(self) -> None:
        self.stack:List[Any] = list()


    def is_empty(self) -> bool:
        return len(self.stack) == 0
    

    def size(self) -> int:
        return len(self.stack)
    

    def push(self, item:Any) -> None:
        self.stack.append(item)
    

    def peek(self) -> Any:
        if self.is_empty():
            raise Exception('Stack is empty')
        return self.stack[len(self.stack) - 1]
    

    def pop(self) -> Any:
        if self.is_empty():
            raise Exception('Stack is empty')
        return self.stack.pop()

size: int = 5
matrix =  [[0 for x in range(size)] for y in range(size)]

'''
0 - not visited
1 - visited
2 - obstacle
'''
# place maze obstacles
matrix[0][2] = 2
matrix[0][3] = 2
matrix[1][0] = 2
matrix[2][2] = 2
matrix[2][3] = 2
matrix[2][4] = 2
matrix[3][1] = 2
matrix[4][3] = 2

# starting position
matrix[0][0] = 1

stack = Stack()
stack.push([0, 0])

def path_finder(matrix:List[List[int]], stack:Stack) -> None:
    _move(matrix, stack)


def _move(matrix:List[List[int]], stack:Stack) -> None:

    if stack.is_empty():
        print('Path not found')
    else:
        x, y = stack.peek()
        if x == len(matrix) - 1 and y == len(matrix[x]) - 1:
            print('Path found')
        elif y < len(matrix[x]) - 1 and matrix[x][y + 1] == 0:
            _add_coordinates_to_stack(matrix, stack, x, y + 1)
            _move(matrix, stack)
        elif y > 0 and matrix[x][y - 1] == 0:
            _add_coordinates_to_stack(matrix, stack, x, y - 1)
            _move(matrix, stack)
        elif x > 0 and matrix[x - 1][y] == 0:
            _add_coordinates_to_stack(matrix, stack, x - 1, y)
            _move(matrix, stack)
        elif x < len(matrix) - 1 and matrix[x + 1][y] == 0: 
            _add_coordinates_to_stack(matrix, stack, x + 1, y)
            _move(matrix, stack)
        else:
            stack.pop()
            _move(matrix, stack)


def _add_coordinates_to_stack(matrix:List[List[int]], stack:Stack, x:int, y:int) -> None:
    matrix[x][y] = 1
    stack.push([x, y])


if __name__ == "__main__":

    path_finder(matrix, stack)

        




================================================
FILE: recursion/palindrome.py
================================================
def palindrome_checker(string: str) -> bool:

    def palindrome_helper(s: str, start: int, end: int) -> bool:
        if start >= end:
            return True
        else:
            if s[start] == s[end]:
                return palindrome_helper(s, start + 1, end - 1)
            else:
                return False

    return palindrome_helper(string, 0, len(string) - 1)


str1: str = "kayak"
str2: str = "aibohphobia"

print(palindrome_checker(str1))
print(palindrome_checker(str2))


def palindrome_checker_iterative(string:str) -> bool:
    is_palindrome: bool = True
    start = 0
    end = len(string) - 1

    while start < end and is_palindrome:
        if string[start] != string[end]:
            is_palindrome = False
        start += 1
        end -= 1
    
    return is_palindrome


print(palindrome_checker_iterative(str1))
print(palindrome_checker_iterative(str2))


def palindrome_checker_slicing(string: str) -> bool:
    if len(string) <= 1:
        return True
    else:
        if string[0] == string[-1]:
            return palindrome_checker_slicing(string[1: -1])
        else:
            return False


print(palindrome_checker_slicing(str1))
print(palindrome_checker_slicing(str2))


================================================
FILE: recursion/reverse-linked-list-iterative-stack.py
================================================
from stack import Stack

class ListNode:
    def __init__(self, payload = None, next: 'ListNode' = None) -> None:
        self.payload = payload
        self.next: 'ListNode' = next



def reverse_list(head: ListNode) -> ListNode:
    if head is None or head.next is None:
        return head

    stack: Stack = Stack()

    while head is not None:
        stack.push(head)
        head = head.next

    dummy: ListNode = ListNode()
    dummy_tmp = dummy

    while not stack.is_empty():
        dummy_tmp.next = stack.pop()
        dummy_tmp = dummy_tmp.next

    dummy_tmp.next = None
    return dummy.next


    


================================================
FILE: recursion/reverse-linked-list-iterative.py
================================================
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def reverse_list(head: ListNode) -> ListNode:
    if head is None or head.next is None:
        return head

    stopped: bool = False
    prev = head.next
    head.next = None
    while not stopped:
        tmp: ListNode = prev.next
        prev.next = head
        head = prev
        if tmp is not None:
            prev = tmp
        else:
            stopped = True

    return prev


================================================
FILE: recursion/reverse-linked-list.py
================================================
class ListNode:
    def __init__(self, val: int = None, next = None):
        self.val = val
        self.next = next


def reverse_linked_list(node: ListNode) -> ListNode:
    if node is None:
        return None
    elif node.next is None:
        return node
    else:
        next_node: ListNode = node.next
        node.next = None
        rest: ListNode = reverse_linked_list(next_node)
        next_node.next = node
        return rest



================================================
FILE: recursion/reverse-list.py
================================================
nums: list = [1, 2, 3, 4, 5]


def reverse_rec(elements: list):

    def reverse_list_helper(values: list, start: int, end: int):
        if start < end:
            values[start], values[end] = values[end], values[start]
            reverse_list_helper(values, start + 1, end - 1)

    reverse_list_helper(elements, 0, len(elements) - 1)


print(nums)
reverse_rec(nums)
print(nums)


def reverse_iterative(elements: list):
    start: int = 0
    end: int = len(elements) - 1
    while start < end:
        elements[start], elements[end] = elements[end], elements[start]
        start += 1
        end -= 1


reverse_iterative(nums)
print(nums)


================================================
FILE: recursion/reverse-string.py
================================================

string: str = "This string will be reversed"


def reverse_string(string: str) -> str:

    def helper(s: str, end: int) -> str:
        if end < 0:
            return ""
        else:
            return s[end] + helper(s, end - 1)

    return helper(string, len(string) - 1)


print(reverse_string(string))


def reverse_string_ex_one(string: str) -> str:
    if len(string) == 0:
        return ""
    else:
        return reverse_string_ex_one(string[1:]) + string[0]


print(reverse_string_ex_one(string))


def reverse_string_ex_two(string: str) -> str:
    if len(string) == 0:
        return ""
    else:
        return string[len(string) - 1] + reverse_string_ex_two(string[0: len(string) - 1])


print(reverse_string_ex_two(string))


================================================
FILE: recursion/stack.py
================================================
class Stack:

    def __init__(self):
        self.items = []

    def is_empty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items) - 1]

    def size(self):
        return len(self.items)

================================================
FILE: recursion/sum-numbers-binary-recursion.py
================================================
numbers: list = [1, 2, 3, 4, 5]


# O(n) time
def sum_numbers(nums: list) -> int:

    def helper(elements: list, start: int, end: int) -> int:
        if start > end:
            return 0
        else:
            index: int = start + (end - start) // 2
            return helper(elements, start, index - 1) + elements[index] + helper(elements, index + 1, end)

    return helper(nums, 0, len(nums) - 1)


print(sum_numbers(numbers))


================================================
FILE: recursion/sum-numbers-pointer.py
================================================
numbers: list = [1, 2, 3, 4, 5]


# O(n) time
def sum_numbers(nums: list) -> int:

    def helper(elements: list, end: int) -> int:
        if end < 0:
            return 0
        else:
            return elements[end] + helper(elements, end - 1)

    return helper(nums, len(nums) - 1)


print(sum_numbers(numbers))


================================================
FILE: recursion/sum-numbers-slicing.py
================================================
numbers: list = [1, 2, 3, 4, 5]


# slicing is O(k), better is solution with pointer
def sum_numbers(nums: list) -> int:
    if len(nums) == 0:
        return 0
    else:
        return nums[0] + sum_numbers(nums[1:])


print(sum_numbers(numbers))


================================================
FILE: recursion/towers-of-hanoi.py
================================================
def print_move(start: str, end: str):
    print("Moving from", start, "to", end)


def towers(number: int, start: str, spare: str, end: str):
    if number == 1:
        print_move(start, end)
    else:
        towers(number - 1, start, end, spare)
        towers(1, start, spare, end)
        towers(number - 1, spare, start, end)


towers(3, "start", "spare", "end")


================================================
FILE: searching/binary-search-iterative.py
================================================

def binary_search(nums: list, target: int) -> bool:
    start: int = 0
    end: int = len(nums) - 1
    found: bool = False
    while start <= end and not found:
        midpoint: int = start + (end - start) // 2
        if nums[midpoint] == target:
            found = True
        else:
            if nums[midpoint] > target:
                end = midpoint - 1
            else:
                start = midpoint + 1
    return found


testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]
print(binary_search(testlist, 3))
print(binary_search(testlist, 13))


================================================
FILE: searching/binary-search-recursive-pointers.py
================================================

def binary_search(nums: list, target: int) -> bool:

    def binary_search_helper(numbers: list, element, start: int, end: int) -> bool:
        if start > end:
            return False
        else:
            midpoint: int = start + (end - start) // 2
            if nums[midpoint] == element:
                return True
            else:
                if nums[midpoint] > element:
                    return binary_search_helper(numbers, element, start, midpoint - 1)
                else:
                    return binary_search_helper(numbers, element, midpoint + 1, end)

    return binary_search_helper(nums, target, 0, len(nums) - 1)


testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]
print(binary_search(testlist, 3))
print(binary_search(testlist, 13))


================================================
FILE: searching/binary-search-recursive.py
================================================
# slicing a list is O(k)
# better is recursive solution with pointers
def binary_search(nums: list, target: int) -> bool:
    if len(nums) == 0:
        return False
    else:
        midpoint: int = len(nums) // 2
        if nums[midpoint] == target:
            return True
        else:
            if nums[midpoint] > target:
                return binary_search(nums[0: midpoint], target)
            else:
                return binary_search(nums[midpoint + 1:], target)


testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]
print(binary_search(testlist, 3))
print(binary_search(testlist, 13))


================================================
FILE: searching/sequential-search-ordered-list.py
================================================

def sequential_search(nums: list, target: int) -> bool:
    found: bool = False
    stopped: bool = False
    i: int = 0
    while i < len(nums) and not found and not stopped:
        if nums[i] == target:
            found = True
        else:
            if nums[i] > target:
                stopped = True
            else:
                i += 1
    return found


testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42]
print(sequential_search(testlist, 3))
print(sequential_search(testlist, 13))


================================================
FILE: searching/sequential-search-unordered-list.py
================================================

def sequential_search(nums: list, target: int) -> bool:
    i: int = 0
    found: bool = False
    while i < len(nums) and not found:
        if nums[i] == target:
            found = True
        else:
            i += 1
    return found


testlist = [1, 2, 32, 8, 17, 19, 42, 13, 0]
print(sequential_search(testlist, 3))
print(sequential_search(testlist, 13))


================================================
FILE: sorting/bubble-sort.py
================================================

array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def bubble_sort(nums: list):
    for i in range(len(nums)):
        for j in range(len(nums) - 1, i, -1):
            if nums[j - 1] > nums[j]:
                nums[j - 1], nums[j] = nums[j], nums[j - 1]


print(array)
bubble_sort(array)
print(array)


================================================
FILE: sorting/insertion-sort.py
================================================

array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def insertion_sort(nums: list):
    for i in range(1, len(nums), +1):
        curr: int = nums[i]
        pos: int = i
        while pos > 0 and nums[pos - 1] > curr:
            nums[pos] = nums[pos - 1]
            pos -= 1
        nums[pos] = curr


print(array)
insertion_sort(array)
print(array)


================================================
FILE: sorting/merge-sort.py
================================================

array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def merge_sort(nums: list):
    if len(nums) < 2:
        return
    midpoint: int = len(nums) // 2
    left: list = nums[0:midpoint]
    right: list = nums[midpoint:]
    merge_sort(left)
    merge_sort(right)
    i = j = k = 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            nums[k] = left[i]
            i += 1
        else:
            nums[k] = right[j]
            j += 1
        k += 1
    while i < len(left):
        nums[k] = left[i]
        i += 1
        k += 1
    while j < len(right):
        nums[k] = right[j]
        j += 1
        k += 1


print(array)
merge_sort(array)
print(array)

================================================
FILE: sorting/quicksort-return-new-array.py
================================================
array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def quick_sort(nums: list) -> list:
    if len(nums) < 2:
        return nums
    pivot_index: int = len(nums) // 2
    pivot_value: int = nums[pivot_index]

    left: list = []
    right: list = []
    for i in range(len(nums)):
        if i != pivot_index:
            if nums[i] < pivot_value:
                left.append(nums[i])
            else:
                right.append(nums[i])
    return quick_sort(left) + [pivot_value] + quick_sort(right)


print(array)
array_sorted: list = quick_sort(array)
print(array_sorted)


================================================
FILE: sorting/quicksort.py
================================================
array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def quick_sort(nums: list, i: int, j: int):
    if i < j:
        left: int = i
        right: int = j
        pointer: int = left
        pivot_index: int = left + (right - left) // 2
        pivot_value: int = nums[pivot_index]

        while pointer <= right:
            if nums[pointer] < pivot_value:
                nums[left], nums[pointer] = nums[pointer], nums[left]
                left += 1
                pointer += 1
            elif nums[pointer] > pivot_value:
                nums[pointer], nums[right] = nums[right], nums[pointer]
                right -= 1
            else:
                pointer += 1

        quick_sort(nums, i, left)
        if pointer > left:
            quick_sort(nums, pointer, j)
        else:
            quick_sort(nums, pointer + 1, j)


print(array)
quick_sort(array, 0, len(array) - 1)
print(array)


================================================
FILE: sorting/selection-sort.py
================================================

array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def selection_sort(nums: list):
    for i in range(0, len(nums), +1):
        min_index: int = i
        for j in range(i + 1, len(nums) - 1, +1):
            if nums[j] < nums[min_index]:
                min_index = j
        if min_index != i:
            nums[min_index], nums[i] = nums[i], nums[min_index]


print(array)
selection_sort(array)
print(array)


================================================
FILE: sorting/short-bubble.py
================================================

array = [54, 26, 93, 17, 77, 31, 44, 55, 20]


def short_bubble(nums: list):
    swapped: bool = True
    dec: int = 1
    while swapped:
        swapped = False
        for i in range(len(nums) - dec):
            if nums[i] > nums[i + 1]:
                nums[i + 1], nums[i] = nums[i], nums[i + 1]
                swapped = True
        dec += 1


print(array)
short_bubble(array)
print(array)


================================================
FILE: stack/examples/balanced-brackets.py
================================================
from stack import Stack


def balanced_brackets(string: str) -> bool:
    stack: Stack = Stack()
    for character in string:
        if character in "([{":
            stack.push(character)
        if character in ")]}":
            if stack.is_empty():
                return False
            if "([{".index(stack.peek()) == ")]}".index(character):
                stack.pop()
            else:
                return False
    
    return stack.is_empty()


print(balanced_brackets('((()))'))  # True
print(balanced_brackets('(()'))  # False
print(balanced_brackets(']()'))  # False
print(balanced_brackets('(])'))  # False


================================================
FILE: stack/examples/number_converter.py
================================================
from stack import Stack


def base_converter(num, base) -> str:
    digits = "0123456789ABCDEF"
    stack: Stack = Stack()
    while num > 0:
        stack.push(digits[num % base])
        num = num // base

    result: str = ""
    while not stack.is_empty():
        result += stack.pop()

    return result


print(base_converter(25, 8))  # 31
print(base_converter(256, 16))  # 100
print(base_converter(26, 26))  # 10


================================================
FILE: stack/examples/stack.py
================================================
class Stack:
    def __init__(self):
        self._stack: list = []

    def is_empty(self) -> bool:
        return len(self._stack) == 0

    def push(self, item):
        self._stack.append(item)

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self._stack.pop()

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        # return self._stack[-1] # python way
        return self._stack[len(self._stack) - 1]

    def size(self) -> int:
        return len(self._stack)


================================================
FILE: stack/stack-array-impl-less-efficient.py
================================================
# this solution is less efficient because
# pop() from end of array is faster than pop(some_other_index)
# append(item) is faster than insert(some_other_index, item)
class Stack:
    def __init__(self):
        self._stack: list = []

    def is_empty(self) -> bool:
        return len(self._stack) == 0

    def push(self, item):
        self._stack.insert(0, item)

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self._stack.pop(0)

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self._stack[0]

    def size(self) -> int:
        return len(self._stack)


def reverse_string(s: str) -> str:
    stack: Stack = Stack()
    for character in s:
        stack.push(character)

    result: str = ""
    while not stack.is_empty():
        result += stack.pop()

    return result


string: str = "This string will be reversed ..."
print(reverse_string(string)) # ... desrever eb lliw gnirts sihT




================================================
FILE: stack/stack-array-impl.py
================================================
class Stack:
    def __init__(self):
        self._stack: list = []

    def is_empty(self) -> bool:
        return len(self._stack) == 0

    def push(self, item):
        self._stack.append(item)

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self._stack.pop()

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        # return self._stack[-1] # python way
        return self._stack[len(self._stack) - 1]

    def size(self) -> int:
        return len(self._stack)


def reverse_string(s: str) -> str:
    stack: Stack = Stack()
    for character in s:
        stack.push(character)

    result: str = ""
    while not stack.is_empty():
        result += stack.pop()

    return result


string: str = "This string will be reversed ..."
print(reverse_string(string)) # ... desrever eb lliw gnirts sihT




================================================
FILE: stack/stack-fixed-size-array-impl.py
================================================
from typing import Any, List


class Stack:

    def __init__(self, capacity: int = 1) -> None:
        self.capacity: int = capacity
        self.stack: List[Any] = [None] * self.capacity
        self.pointer: int = -1

    def size(self) -> int:
        return self.pointer + 1

    def is_empty(self) -> bool:
        return self.pointer == -1

    def is_full(self) -> bool:
        return self.pointer == self.capacity - 1

    def push(self, item:Any):
        if self.is_full():
            raise Exception('Stack is full')
        self.pointer += 1
        self.stack[self.pointer] = item

    def pop(self) -> Any:
        if self.is_empty():
            raise Exception('Stack is empty')
        item = self.stack[self.pointer]
        self.pointer -= 1
        return item

    def peek(self) -> Any:
        if self.is_empty():
            raise Exception('Stack is empty')
        return self.stack[self.pointer]


def reverse_string(s: str) -> str:
    stack: Stack = Stack(len(s))
    for character in s:
        stack.push(character)

    result: str = ""
    while not stack.is_empty():
        result += stack.pop()

    return result


string: str = "This string will be reversed ..."
print(reverse_string(string)) # ... desrever eb lliw gnirts sihT




================================================
FILE: stack/stack-linked-list-impl.py
================================================
class Node:
    def __init__(self, key=None, next=None):
        self.key = key
        self.next = next


class Stack:
    def __init__(self):
        self._head: Node = None
        self._count: int = 0

    def is_empty(self) -> bool:
        return self._head is None

    def push(self, item):
        self._head = Node(item, self._head)
        self._count += 1

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        ret_value = self._head.key
        self._head = self._head.next
        self._count -= 1
        return ret_value

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self._head.key

    def size(self) -> int:
        return len(self._count)


def reverse_string(s: str) -> str:
    stack: Stack = Stack()
    for character in s:
        stack.push(character)

    result: str = ""
    while not stack.is_empty():
        result += stack.pop()

    return result


string: str = "This string will be reversed ..."
print(reverse_string(string)) # ... desrever eb lliw gnirts sihT




================================================
FILE: stack/stack_two_queues.py
================================================
class Queue:
    def __init__(self):
        self._queue = []

    def enqueue(self, item):
        self._queue.insert(0, item)

    def dequeue(self):
        if self.is_empty():
            raise Exception("Queue is empty")
        return self._queue.pop()

    def size(self) -> int:
        return len(self._queue)

    def is_empty(self) -> bool:
        return len(self._queue) == 0
        
    def peek(self) -> int:
    	if self.is_empty():
            raise Exception("Queue is empty")
    	return self._queue[len(self._queue) - 1]

class MyStack:

    def __init__(self):
        self.input = Queue()
        self.output = Queue()

    def empty(self) -> bool:
        return self.input.is_empty()

    def push(self, item):
        self.input.enqueue(item)

    def pop(self):
        if self.empty():
            raise Exception("Stack is empty")
        while self.input.size() > 1 :
            self.output.enqueue(self.input.dequeue())
        
        val = self.input.dequeue()
        while not self.output.is_empty():
            self.input.enqueue(self.output.dequeue())
        
        return val

    def top(self):
        if self.empty():
            raise Exception("Stack is empty")
        while self.input.size() > 1 :
            self.output.enqueue(self.input.dequeue())
        
        val = self.input.peek()
        self.output.enqueue(self.input.dequeue())
        while not self.output.is_empty():
            self.input.enqueue(self.output.dequeue())
        
        return val

    def size(self) -> int:
        return self.input.size()

        



================================================
FILE: stack_two_queues.py
================================================
from typing import Any, List


class Queue:

    def __init__(self) -> None:
        self.queue:List[Any] = []

    def size(self) -> int:
        return len(self.queue)

    def is_empty(self) -> bool:
        return len(self.queue) == 0

    def enqueue(self, item:Any) -> None:
        self.queue.insert(0, item)

    def dequeue(self) -> Any:
        if self.is_empty():
            raise Exception('Queue is empty')
        return self.queue.pop()



class Stack:

    def __init__(self):
        self.input: Queue = Queue()
        self.output:Queue = Queue()
        

    def push(self, x: Any) -> None:
        self.input.enqueue(x)
        

    def pop(self) -> Any:
        if self.input.is_empty():
            raise Exception('Queue is empty')
        
        while self.input.size() > 1:
            self.output.enqueue(self.input.dequeue())
            
        item:Any = self.input.dequeue()

        while not self.output.is_empty():
            self.input.enqueue(self.output.dequeue())
            
        return item


    def peek(self) -> Any:
        if self.input.is_empty():
            raise Exception('Queue is empty')
            
        while self.input.size() > 1:
            self.output.enqueue(self.input.dequeue())
            
        item:Any = self.input.dequeue()
        self.output.enqueue(item)
        
        while not self.output.is_empty():
            self.input.enqueue(self.output.dequeue())
        return item
        

    def is_empty(self) -> bool:
        return self.input.is_empty()


================================================
FILE: substring-search/brute_force.py
================================================


# O(m * n)
# m - length of the text
# n - length of pattern
def search(text: str, pattern: str) -> int:
    
    t:int = 0
    last_index: int = len(text) - len(pattern) + 1
    
    while t <= len(text) - len(pattern):
        p:int = 0
        while p < len(pattern):
            if text[t + p] != pattern[p]:
                break
            else:
                p += 1
        if p == len(pattern):
            return t
        t += 1
    
    return -1


        

================================================
FILE: trees/avl-tree.py
================================================
class TreeNode:
    def __init__(self, key=None, value=None, parent=None, left=None, right=None,
                 left_subtree_height: int = 0, right_subtree_height: int = 0, balance_factor: int = 0):
        self.key = key
        self.value = value
        self.parent = parent
        self.left = left
        self.right = right
        self.left_subtree_height: int = left_subtree_height
        self.right_subtree_height: int = right_subtree_height
        self.balance_factor : int = balance_factor

    def has_left_child(self) -> bool:
        return self.left is not None

    def has_right_child(self) -> bool:
        return self.right is not None

    def has_both_children(self) -> bool:
        return self.has_left_child() and self.has_right_child()

    def is_leaf(self) -> bool:
        return not self.has_left_child() and not self.has_right_child()

    def is_root(self) -> bool:
        return self.parent is None

    def has_parent(self) -> bool:
        return self.parent is not None

    def is_left_child(self) -> bool:
        return self.parent.left == self

    def is_right_child(self) -> bool:
        return self.parent.right == self

    def find_min(self):
        if self is None:
            return None
        if self.has_left_child():
            return self.left.find_min()
        else:
            return self

    def find_max(self):
        if self is None:
            return None
        node = self
        while node.right is not None:
            node = node.right
        return node


class AVLTree:
    def __init__(self):
        self.root: TreeNode = None
        self.elements: int = 0

    def size(self) -> int:
        return self.elements

    def is_empty(self) -> bool:
        return self.root is None

    def put(self, key, value):
        if self.is_empty():
            self.root = TreeNode(key, value)
            self.elements += 1
        else:
            self._put(self.root, key, value)

    def _put(self, root: TreeNode, key, value):
        if root.key == key:
            root.value = value
        elif key < root.key:
            if root.has_left_child():
                self._put(root.left, key, value)
            else:
                root.left = TreeNode(key, value, root)
                self.elements += 1
                self._update_balance_factor(root)
        else:
            if root.has_right_child():
                self._put(root.right, key, value)
            else:
                root.right = TreeNode(key, value, root)
                self.elements += 1
                self._update_balance_factor(root)

    def get(self, key) -> TreeNode:
        if self.is_empty():
            return None
        else:
            return self._get(self.root, key)

    def _get(self, root: TreeNode, key) -> TreeNode:
        if root.key == key:
            return root
        elif key < root.key:
            if root.has_left_child():
                return self._get(root.left, key)
            else:
                return None
        else:
            if root.has_right_child():
                return self._get(root.right, key)
            else:
                return None

    def contains(self, key) -> bool:
        if self.is_empty():
            return None
        found: bool = False
        node: TreeNode = self.root
        while node is not None and not found:
            if node.key == key:
                found = True
            elif key < node.key:
                node = node.left
            else:
                node = node.right
        return found

    def delete(self, key):
        node_to_delete: TreeNode = self.get(key)
        if node_to_delete is None:
            return
        if node_to_delete.is_root():
            if node_to_delete.is_leaf():
                self.root = None
                self.elements -= 1
            elif node_to_delete.has_both_children():
                max_node: TreeNode = node_to_delete.left.find_max()
                tmp_key = max_node.key
                tmp_value = max_node.value
                self.delete(tmp_key)
                # keep pointer to that node, not root, root might change
                node_to_delete.key = tmp_key
                node_to_delete.value = tmp_value
            else:
                if node_to_delete.has_left_child():
                    self.root = node_to_delete.left
                else:
                    self.root = node_to_delete.right
                self.root.parent = None
                self.elements -= 1
        else:
            parent: TreeNode = None
            if node_to_delete.is_leaf():
                parent = node_to_delete.parent
                if node_to_delete.is_left_child():
                    node_to_delete.parent.left = None
                else:
                    node_to_delete.parent.right = None
                self.elements -= 1
            elif node_to_delete.has_both_children():
                max_node: TreeNode = node_to_delete.left.find_max()
                tmp_key = max_node.key
                tmp_value = max_node.value
                self.delete(tmp_key)
                node_to_delete.key = tmp_key
                node_to_delete.value = tmp_value
            elif node_to_delete.has_left_child():
                parent = node_to_delete.parent
                self.elements -= 1
                if node_to_delete.is_left_child():
                    node_to_delete.parent.left = node_to_delete.left
                else:
                    node_to_delete.parent.right = node_to_delete.left
                node_to_delete.left.parent = node_to_delete.parent
            else:
                parent = node_to_delete.parent
                self.elements -= 1
                if node_to_delete.is_left_child():
                    node_to_delete.parent.left = node_to_delete.right
                else:
                    node_to_delete.parent.right = node_to_delete.right
                node_to_delete.right.parent = node_to_delete.parent
            if parent is not None:
                self._update_balance_factor(parent)

    def find_min(self) -> TreeNode:
        if self.is_empty():
            return None
        node: TreeNode = self.root
        while node.left is not None:
            node = node.left
        return node

    def find_max(self) -> TreeNode:
        if self.is_empty():
            return None
        node: TreeNode = self.root
        while node.right is not None:
            node = node.right
        return node

    def _update_balance_factor(self, root: TreeNode):
        old_balance_factor: int = root.balance_factor
        if root.has_left_child():
            root.left_subtree_height = max(root.left.left_subtree_height, root.left.right_subtree_height) + 1
        else:
            root.left_subtree_height = 0
        if root.has_right_child():
            root.right_subtree_height = max(root.right.left_subtree_height, root.right.right_subtree_height) + 1
        else:
            root.right_subtree_height = 0
        root.balance_factor = root.left_subtree_height - root.right_subtree_height
        if root.balance_factor < -1 or root.balance_factor > 1:
            self._rebalance(root)
            return
        if root.balance_factor != old_balance_factor and root.has_parent():
            self._update(root.parent)

    def _rebalance(self, root: TreeNode):
        if root.balance_factor < 0:
            if root.right.balance_factor > 0:
                self._rotate_right(root.right)
            else:
                self._rotate_left(root)
        else:
            if root.left.balance_factor < 0:
                self._rotate_left(root.left)
            else:
                self._rotate_right(root)

    def _rotate_left(self, root: TreeNode):
        old_root: TreeNode = root
        new_root: TreeNode = old_root.right
        old_root.right = new_root.left
        if new_root.has_left_child():
            new_root.left.parent = old_root
        new_root.parent = old_root.parent
        if old_root.has_parent():
            if old_root.is_left_child():
                old_root.parent.left = new_root
            else:
                old_root.parent.right = new_root
        else:
            self.root = new_root
        old_root.parent = new_root
        new_root.left = old_root
        self._update_balance_factor(old_root)

    def _rotate_right(self, root: TreeNode):
        old_root: TreeNode = root
        new_root: TreeNode = old_root.left
        old_root.left = new_root.right
        if new_root.has_right_child():
            new_root.right.parent = old_root
        new_root.parent = old_root.parent
        if old_root.has_parent():
            if old_root.is_left_child():
                old_root.parent.left = new_root
            else:
                old_root.parent.right = new_root
        else:
            self.root = new_root
        old_root.parent = new_root
        new_root.right = old_root
        self._update_balance_factor(old_root)





================================================
FILE: trees/binary-heap.py
================================================

# min heap
class BinaryHeap:
    def __init__(self):
        self.pointer: int = 0
        self.heap: list = [None]

    def is_empty(self) -> bool:
        return self.pointer == 0

    def insert(self, item):
        self.heap.append(item)
        self.pointer += 1
        self.perc_up(self.pointer)

    def perc_up(self, index: int):
        while index // 2 > 0:
            if self.heap[index] < self.heap[index // 2]:
                self.heap[index], self.heap[index // 2] = self.heap[index // 2], self.heap[index]
            index = index // 2

    def get_min(self):
        if self.is_empty():
            raise Exception("Heap is empty")
        return self.heap[1]

    def delete_min(self):
        if self.is_empty():
            raise Exception("Heap is empty")
        ret_value: int = self.heap[1]
        self.heap[1] = self.heap[self.pointer]
        self.heap.pop()
        self.pointer -= 1
        self.perc_down(1)
        return ret_value

    def perc_down(self, index: int):
        while index * 2 <= self.pointer:
            swap_index: int = self.find_swap_index(index)
            if self.heap[swap_index] < self.heap[index]:
                self.heap[swap_index], self.heap[index] = self.heap[index], self.heap[swap_index]
            index = swap_index

    def find_swap_index(self, index: int) -> int:
        if index * 2 + 1 > self.pointer:
            return index * 2
        else:
            if self.heap[index * 2] <= self.heap[index * 2 + 1]:
                return index * 2
            else:
                return index * 2 + 1

    def build_heap(self, nums: list):
        for n in nums:
            self.insert(n)


h: BinaryHeap = BinaryHeap()
h.insert(10)
h.insert(1)
h.insert(3)
print(h.heap)  # [None, 1, 10, 3]
print(h.get_min())  # 1

while not h.is_empty():
    print(h.delete_min())


================================================
FILE: trees/binary-search-tree.py
================================================
class TreeNode:
    def __init__(self, key=None, value=None, parent=None, left=None, right=None):
        self.key = key
        self.value = value
        self.parent = parent
        self.left = left
        self.right = right

    def has_left_child(self) -> bool:
        return self.left is not None

    def has_right_child(self) -> bool:
        return self.right is not None

    def has_both_children(self) -> bool:
        return self.has_left_child() and self.has_right_child()

    def is_leaf(self) -> bool:
        return not self.has_left_child() and not self.has_right_child()

    def is_root(self) -> bool:
        return self.parent is None

    def has_parent(self) -> bool:
        return self.parent is not None

    def is_left_child(self) -> bool:
        if self.parent is None:
            return False
        return self.parent.left == self

    def is_right_child(self) -> bool:
        if self.parent is None:
            return False
        return self.parent.right == self

    def find_min(self):
        if self is None:
            return None
        if self.has_left_child():
            return self.left.find_min()
        else:
            return self

    def find_max(self):
        if self is None:
            return None
        node = self
        while node.right is not None:
            node = node.right
        return node


class BinarySearchTree:
    def __init__(self):
        self.root: TreeNode = None
        self.elements: int = 0

    def size(self) -> int:
        return self.elements

    def is_empty(self) -> bool:
        return self.root is None

    def put(self, key, value):
        if self.is_empty():
            self.root = TreeNode(key, value)
            self.elements += 1
        else:
            self._put(self.root, key, value)

    def _put(self, root: TreeNode, key, value):
        if root.key == key:
            root.value = value
        elif key < root.key:
            if root.has_left_child():
                self._put(root.left, key, value)
            else:
                root.left = TreeNode(key, value, root)
                self.elements += 1
        else:
            if root.has_right_child():
                self._put(root.right, key, value)
            else:
                root.right = TreeNode(key, value, root)
                self.elements += 1

    def get(self, key) -> TreeNode:
        if self.is_empty():
            return None
        else:
            return self._get(self.root, key)

    def _get(self, root: TreeNode, key) -> TreeNode:
        if root.key == key:
            return root
        elif key < root.key:
            if root.has_left_child():
                return self._get(root.left, key)
            else:
                return None
        else:
            if root.has_right_child():
                return self._get(root.right, key)
            else:
                return None

    def contains(self, key) -> bool:
        if self.is_empty():
            return False
        found: bool = False
        node: TreeNode = self.root
        while node is not None and not found:
            if node.key == key:
                found = True
            elif key < node.key:
                node = node.left
            else:
                node = node.right
        return found

    def delete(self, key):
        node_to_delete: TreeNode = self.get(key)
        if node_to_delete is None:
            return
        if node_to_delete.is_root():
            if node_to_delete.is_leaf():
                self.root = None
                self.elements -= 1
            elif node_to_delete.has_both_children():
                max_node: TreeNode = node_to_delete.left.find_max()
                tmp_key = max_node.key
                tmp_value = max_node.value
                self.delete(tmp_key)
                node_to_delete.key = tmp_key
                node_to_delete.value = tmp_value
            else:
                if node_to_delete.has_left_child():
                    self.root = node_to_delete.left
                else:
                    self.root = node_to_delete.right
                self.root.parent = None
                self.elements -= 1
        else:
            if node_to_delete.is_leaf():
                if node_to_delete.is_left_child():
                    node_to_delete.parent.left = None
                else:
                    node_to_delete.parent.right = None
                self.elements -= 1
            elif node_to_delete.has_both_children():
                max_node: TreeNode = node_to_delete.left.find_max()
                tmp_key = max_node.key
                tmp_value = max_node.value
                self.delete(tmp_key)
                node_to_delete.key = tmp_key
                node_to_delete.value = tmp_value
            elif node_to_delete.has_left_child():
                self.elements -= 1
                if node_to_delete.is_left_child():
                    node_to_delete.parent.left = node_to_delete.left
                else:
                    node_to_delete.parent.right = node_to_delete.left
                node_to_delete.left.parent = node_to_delete.parent
            else:
                self.elements -= 1
                if node_to_delete.is_left_child():
                    node_to_delete.parent.left = node_to_delete.right
                else:
                    node_to_delete.parent.right = node_to_delete.right
                node_to_delete.right.parent = node_to_delete.parent

    def find_min(self) -> TreeNode:
        if self.is_empty():
            return None
        node: TreeNode = self.root
        while node.left is not None:
            node = node.left
        return node

    def find_max(self) -> TreeNode:
        if self.is_empty():
            return None
        node: TreeNode = self.root
        while node.right is not None:
            node = node.right
        return node


================================================
FILE: trees/class-representation.py
================================================
class TreeNode:
    def __init__(self, key=None, left=None, right=None):
        self.key = key
        self.left = left
        self.right = right

    def insert_root_value(self, key=None):
        self.key = key

    def insert_left(self, key=None):
        self.left = TreeNode(key, self.left)

    def insert_right(self, key=None):
        self.right = TreeNode(key, None, self.right)

    def get_root_value(self):
        return self.key

    def get_left_child(self):
        return self.left

    def get_right_child(self):
        return self.right


# Write a function build_tree that returns a tree
# using the list of lists functions that look like this :
#      a
#    /   \
#   b     c
#    \   / \
#     d e   f


def build_tree() -> TreeNode:
    tree: TreeNode = TreeNode('a')
    tree.insert_right('f')
    tree.insert_right('c')
    tree.get_right_child().insert_left('e')

    tree.insert_left('b')
    tree.get_left_child().insert_right('d')

    return tree


binary_tree: TreeNode = build_tree()


def print_tree(tree: TreeNode):
    if tree is not None:
        print_tree(tree.get_left_child())
        print(tree.key, end=", ")
        print_tree(tree.get_right_child())


print_tree(binary_tree)

# ['a', 
#     ['b',
#         [],
#         ['d', [], []]],
#     ['c',
#         ['e', [], []],
#         ['f', [], []]
#     ]
# ]


================================================
FILE: trees/list-representation.py
================================================

def create_tree(root=None) -> list:
    return [None, [], []]


def insert_root(tree: list, root=None):
    tree[0] = root


def insert_left(tree: list, root=None):
    left_child: list = tree.pop(1)
    tree.insert(1, [root, left_child, []])


def insert_right(tree: list, root=None):
    right_child: list = tree.pop(2)
    tree.insert(2, [root, [], right_child])


def get_root(tree: list):
    return tree[0]


def get_left_child(tree: list) -> list:
    return tree[1]


def get_right_child(tree: list) -> list:
    return tree[2]


# Write a function build_tree that returns a tree
# using the list of lists functions that look like this :
#      a
#    /   \
#   b     c
#    \   / \
#     d e   f


def build_tree() -> list:
    tree: list = create_tree()
    insert_root(tree, 'a')

    insert_right(tree, 'f')
    insert_right(tree, 'c')
    insert_left(get_right_child(tree), 'e')

    insert_left(tree, 'b')
    insert_right(get_left_child(tree), 'd')

    return tree


binary_tree: list = build_tree()
print(binary_tree)


# ['a',
#     ['b',
#         [],
#         ['d', [], []]],
#     ['c',
#         ['e', [], []],
#         ['f', [], []]
#     ]
# ]


================================================
FILE: trees/parse-tree.py
================================================

from stack import Stack
import operator
import re


class TreeNode:
    def __init__(self, key=None, left=None, right=None):
        self.key = key
        self.left = left
        self.right = right

    def insert_root_value(self, key=None):
        self.key = key

    def insert_left(self, key=None):
        self.left = TreeNode(key, self.left)

    def insert_right(self, key=None):
        self.right = TreeNode(key, None, self.right)

    def get_root_value(self):
        return self.key

    def get_left_child(self):
        return self.left

    def get_right_child(self):
        return self.right


opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}

pattern = re.compile("[0-9]")

string = "( ( 10 + 5 ) * 3 )"


def tree_parser(s: str) -> TreeNode:
    arr: list = s.split()
    stack: Stack = Stack()
    node: TreeNode = TreeNode()
    current_node: TreeNode = node
    stack.push(node)
    for e in arr:
        if e == "(":
            current_node.insert_left()
            stack.push(current_node)
            current_node = current_node.get_left_child()
        elif e in "+-*/":
            current_node.insert_root_value(e)
            current_node.insert_right()
            stack.push(current_node)
            current_node = current_node.get_right_child()
        elif pattern.match(e):
            current_node.insert_root_value(int(e))
            current_node = stack.pop()
        elif e == ")":
            current_node = stack.pop()
        else:
            raise Exception()
    return node


tree_node: TreeNode = tree_parser(string)


def evaluate(node: TreeNode) -> int:
    if node.get_left_child() is not None and node.get_right_child() is not None:
        f = opers[node.get_root_value()]
        return f(evaluate(node.get_left_child()), evaluate(node.get_right_child()))
    else:
        return node.get_root_value()


print(evaluate(tree_node))  # 45


================================================
FILE: trees/stack.py
================================================
class Stack:
    def __init__(self):
        self.items = []

    def is_empty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items) - 1]

    def size(self):
        return len(self.items)


================================================
FILE: trees/tree-traversal/functions.py
================================================
from treenode import TreeNode

node: TreeNode = TreeNode('a')
node.insert_left('b')
node.insert_left('c')
node.get_left_child().insert_left('f')

node.insert_right('k')
node.insert_right('j')

print("\nInorder")
node.inorder()
print("\nPreorder")
node.preorder()
print("\nPostorder")
node.postorder()

# functions for tree traversal


def inorder(tree: TreeNode):
    if tree is not None:
        inorder(tree.left)
        print(tree.key, end=", ")
        inorder(tree.right)


def preorder(tree: TreeNode):
    if tree is not None:
        print(tree.key, end=", ")
        preorder(tree.left)
        preorder(tree.right)


def postorder(tree: TreeNode):
    if tree is not None:
        postorder(tree.left)
        postorder(tree.right)
        print(tree.key, end=", ")


print("\nInorder")
inorder(node)
print("\nPreorder")
preorder(node)
print("\nPostorder")
postorder(node)


================================================
FILE: trees/tree-traversal/inorder-traversal-example.py
================================================

from stack import Stack
import operator
import re
from treenode import TreeNode

opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}

pattern = re.compile("[0-9]")

string = "( ( 10 + 5 ) * 3 )"


def tree_parser(s: str) -> TreeNode:
    arr: list = s.split()
    stack: Stack = Stack()
    node: TreeNode = TreeNode()
    current_node: TreeNode = node
    stack.push(node)
    for e in arr:
        if e == "(":
            current_node.insert_left()
            stack.push(current_node)
            current_node = current_node.get_left_child()
        elif e in "+-*/":
            current_node.insert_root_value(e)
            current_node.insert_right()
            stack.push(current_node)
            current_node = current_node.get_right_child()
        elif pattern.match(e):
            current_node.insert_root_value(int(e))
            current_node = stack.pop()
        elif e == ")":
            current_node = stack.pop()
        else:
            raise Exception()
    return node


tree_node: TreeNode = tree_parser(string)


def inorder(node: TreeNode) -> str:
    ret_value: str = ""
    if node.get_left_child() is not None:
        ret_value += "(" + inorder(node.get_left_child())
    ret_value += str(node.get_root_value())
    if node.get_right_child() is not None:
        ret_value += inorder(node.get_right_child()) + ")"

    return ret_value


print(inorder(tree_node))  # ((10+5)*3)



================================================
FILE: trees/tree-traversal/postorder-traversal-example.py
================================================

from stack import Stack
import operator
import re
from treenode import TreeNode

opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}

pattern = re.compile("[0-9]")

string = "( ( 10 + 5 ) * 3 )"


def tree_parser(s: str) -> TreeNode:
    arr: list = s.split()
    stack: Stack = Stack()
    node: TreeNode = TreeNode()
    current_node: TreeNode = node
    stack.push(node)
    for e in arr:
        if e == "(":
            current_node.insert_left()
            stack.push(current_node)
            current_node = current_node.get_left_child()
        elif e in "+-*/":
            current_node.insert_root_value(e)
            current_node.insert_right()
            stack.push(current_node)
            current_node = current_node.get_right_child()
        elif pattern.match(e):
            current_node.insert_root_value(int(e))
            current_node = stack.pop()
        elif e == ")":
            current_node = stack.pop()
        else:
            raise Exception()
    return node


tree_node: TreeNode = tree_parser(string)


def postorder(node: TreeNode) -> str:
    if node is None:
        return None
    left = postorder(node.get_left_child())
    right = postorder(node.get_right_child())
    if left is not None and right is not None:
        f = opers[node.get_root_value()]
        return f(left, right)
    else:
        return node.get_root_value()


print(postorder(tree_node))  # 45



================================================
FILE: trees/tree-traversal/preorder-traversal-example.py
================================================

from stack import Stack
import operator
import re
from treenode import TreeNode

opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}

pattern = re.compile("[0-9]")

string = "( ( 10 + 5 ) * 3 )"


def tree_parser(s: str) -> TreeNode:
    arr: list = s.split()
    stack: Stack = Stack()
    node: TreeNode = TreeNode()
    current_node: TreeNode = node
    stack.push(node)
    for e in arr:
        if e == "(":
            current_node.insert_left()
            stack.push(current_node)
            current_node = current_node.get_left_child()
        elif e in "+-*/":
            current_node.insert_root_value(e)
            current_node.insert_right()
            stack.push(current_node)
            current_node = current_node.get_right_child()
        elif pattern.match(e):
            current_node.insert_root_value(int(e))
            current_node = stack.pop()
        elif e == ")":
            current_node = stack.pop()
        else:
            raise Exception()
    return node


tree_node: TreeNode = tree_parser(string)


def preorder(node: TreeNode, space: int = 0):
    if node is not None:
        print(" " * space, node.key)
        preorder(node.get_left_child(), space + 2)
        preorder(node.get_right_child(), space + 2)


preorder(tree_node)



================================================
FILE: trees/tree-traversal/stack.py
================================================
class Stack:
    def __init__(self):
        self.items = []

    def is_empty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items) - 1]

    def size(self):
        return len(self.items)


================================================
FILE: trees/tree-traversal/treenode.py
================================================
class TreeNode:
    def __init__(self, key=None, left=None, right=None):
        self.key = key
        self.left = left
        self.right = right

    def insert_root_value(self, key=None):
        self.key = key

    def insert_left(self, key=None):
        self.left = TreeNode(key, self.left)

    def insert_right(self, key=None):
        self.right = TreeNode(key, None, self.right)

    def get_root_value(self):
        return self.key

    def get_left_child(self):
        return self.left

    def get_right_child(self):
        return self.right

    def inorder(self):
        if self.get_left_child() is not None:
            self.get_left_child().inorder()
        print(self.key, end=", ")
        if self.get_right_child() is not None:
            self.get_right_child().inorder()

    def preorder(self):
        print(self.key, end=", ")
        if self.get_left_child() is not None:
            self.get_left_child().preorder()
        if self.get_right_child() is not None:
            self.get_right_child().preorder()

    def postorder(self):
        if self.get_left_child() is not None:
            self.get_left_child().postorder()
        if self.get_right_child() is not None:
            self.get_right_child().postorder()
        print(self.key, end=", ")


================================================
FILE: trie/trie.py
================================================

class TrieNode:
    def __init__(self, key, parent = None, children: dict = {}):
        self.key = key
        self.parent = parent
        self.children:dict = {}
        self.endchar: bool = False

class Trie:
    def __init__(self):
        self.root: TrieNode = TrieNode(None)

    def insert(self, string: str):
        current: TrieNode = self.root
        for character in string:
            if character not in current.children:
                current.children[character] = TrieNode(character, current)
            current = current.children[character]
        current.endchar = True

    def contains(self, string: str)->bool:
        current: TrieNode = self.root
        for character in string:
            if character not in current.children:
                current = None
                break
            current = current.children[character]
        if current is None:
            return False
        return current.endchar

    def delete(self, string: str):
        current: TrieNode = self.root
        for character in string:
            if character not in current.children:
                current = None
                break
            current = current.children[character]
        if current is None:
            return
        current.endchar = False
        parent: TrieNode = current.parent
        while parent is not None and not current.endchar and len(current.children) == 0:
            del(parent.children[current.key])
            current = parent
            parent = current.parent

    def prefix(self, prefix: str)->list:
        current: TrieNode = self.root
        for character in prefix:
            if character not in current.children:
                current = None
                break
            current = current.children[character]
        if current is None:
            return
        words: list = []
        self.helper(current, words, prefix)
        return words

    def helper(self, node: TrieNode, words: list, currentWord: str):
        if node is None:
            return
        if node.endchar:
            words.append(currentWord)
        for key in node.children:
            self.helper(node.children[key], words, currentWord + key)

    def allWords(self)->list:
        words: list = []
        self.helper(self.root, words, "")
        return words

    def count(self)->int:
        return self.countHelper(self.root)

    def countHelper(self, node: TrieNode)->int:
        if node is None:
            return 0
        sum: int = 0
        if node.endchar:
            sum += 1
        for character in node.children:
            sum += self.countHelper(node.children[character])
        return sum

trie = Trie()
trie.insert("javascript")
trie.insert("java")
trie.insert("scala")
trie.insert("scale")
trie.insert("scalable")
trie.insert("perl")

print("Contains 'javascript' : ", trie.contains("javascript"))
print("Contains 'java' : ", trie.contains("java"))
print("Contains 'ruby' : ", trie.contains("ruby"))

#trie.delete("java")
trie.delete("javascript")


print("Contains 'javascript' : ", trie.contains("javascript"))
print("Contains 'java' : ", trie.contains("java"))
print("Contains 'ruby' : ", trie.contains("ruby"))

print(trie.prefix("scal")) # ['scala', 'scalable', 'scale']
print(trie.prefix("java")) # ['java']

print("All words", trie.allWords()) # All words ['java', 'scala', 'scalable', 'scale', 'perl']
print("Count : ", trie.count())
Download .txt
gitextract_x7v3a_w5/

├── .gitignore
├── LICENSE
├── README.md
├── analysis/
│   ├── anagrams-linear-solution.py
│   ├── anagrams-loglinear-solution.py
│   ├── anagrams-quadratic-solution.py
│   ├── time-iterative-approach.py
│   └── time-noniterative-approach.py
├── deque/
│   ├── circular-deque.py
│   ├── deque.py
│   └── deque_linked_list_impl.py
├── graphs/
│   ├── bellman-ford/
│   │   └── graph.py
│   ├── bellman-ford-negative-weight-cycle/
│   │   └── graph.py
│   ├── breadth-first-search/
│   │   ├── graph.py
│   │   ├── main.py
│   │   └── queue.py
│   ├── cycle-detection/
│   │   ├── Cycle.md
│   │   ├── cycle-directed-graph/
│   │   │   ├── graph.py
│   │   │   └── main.py
│   │   └── cycle-undirected-graph/
│   │       ├── graph.py
│   │       └── main.py
│   ├── depth-first-search/
│   │   ├── depth-first-search/
│   │   │   ├── graph.py
│   │   │   ├── main.py
│   │   │   └── stack.py
│   │   └── depth-first-search-recursive/
│   │       ├── graph.py
│   │       └── main.py
│   ├── dijkstra/
│   │   ├── adjacency-list-impl/
│   │   │   ├── main.py
│   │   │   └── vertex.py
│   │   ├── matrix-impl/
│   │   │   ├── graph.py
│   │   │   ├── main.py
│   │   │   └── vertex.py
│   │   └── priority-queue-impl-adjacency-map/
│   │       ├── graph.py
│   │       ├── main.py
│   │       ├── priorityqueue.py
│   │       └── vertex.py
│   ├── is-graph-bipartite/
│   │   ├── graph.py
│   │   ├── main.py
│   │   └── queue.py
│   ├── kosarajus-algorithm/
│   │   ├── graph.py
│   │   ├── main.py
│   │   └── stack.py
│   ├── minimum-spanning-tree/
│   │   ├── breadth-first-search/
│   │   │   ├── graph.py
│   │   │   ├── main.py
│   │   │   └── queue.py
│   │   ├── kruskals-algorithm/
│   │   │   ├── graph.py
│   │   │   └── main.py
│   │   └── prims-algorithm/
│   │       ├── graph.py
│   │       ├── main.py
│   │       ├── priorityqueue.py
│   │       └── vertex.py
│   ├── topological-sorting/
│   │   ├── graph.py
│   │   └── main.py
│   └── union-find/
│       ├── number-of-connected-components/
│       │   └── graph.py
│       └── union-find-path-compression/
│           ├── graph.py
│           ├── main.py
│           └── vertex.py
├── hash-table/
│   ├── chaining.py
│   └── linear-probing.py
├── linked-lists/
│   ├── circular-doubly-linked-list/
│   │   ├── list.py
│   │   └── node.py
│   ├── circular-singly-linked-list/
│   │   ├── list.py
│   │   └── node.py
│   ├── doubly-linked-list/
│   │   ├── list.py
│   │   └── node.py
│   └── singly-linked-list/
│       ├── list.py
│       └── node.py
├── queue/
│   ├── circular-queue-fixed-size-array-impl.py
│   ├── queue-array-impl.py
│   ├── queue-fixed-size-array-impl.py
│   ├── queue-linked-list-impl.py
│   └── queue-two-stacks-impl.py
├── recursion/
│   ├── convert-number-iterative.py
│   ├── convert-number.py
│   ├── factorial.py
│   ├── fibonacci-iterative.py
│   ├── fibonacci-memoization.py
│   ├── fibonacci-recursive-worst-solution.py
│   ├── fibonacci-recursive.py
│   ├── fibonacci-sum-iterative.py
│   ├── fibonacci-sum-recursive.py
│   ├── maze.py
│   ├── palindrome.py
│   ├── reverse-linked-list-iterative-stack.py
│   ├── reverse-linked-list-iterative.py
│   ├── reverse-linked-list.py
│   ├── reverse-list.py
│   ├── reverse-string.py
│   ├── stack.py
│   ├── sum-numbers-binary-recursion.py
│   ├── sum-numbers-pointer.py
│   ├── sum-numbers-slicing.py
│   └── towers-of-hanoi.py
├── searching/
│   ├── binary-search-iterative.py
│   ├── binary-search-recursive-pointers.py
│   ├── binary-search-recursive.py
│   ├── sequential-search-ordered-list.py
│   └── sequential-search-unordered-list.py
├── sorting/
│   ├── bubble-sort.py
│   ├── insertion-sort.py
│   ├── merge-sort.py
│   ├── quicksort-return-new-array.py
│   ├── quicksort.py
│   ├── selection-sort.py
│   └── short-bubble.py
├── stack/
│   ├── examples/
│   │   ├── balanced-brackets.py
│   │   ├── number_converter.py
│   │   └── stack.py
│   ├── stack-array-impl-less-efficient.py
│   ├── stack-array-impl.py
│   ├── stack-fixed-size-array-impl.py
│   ├── stack-linked-list-impl.py
│   └── stack_two_queues.py
├── stack_two_queues.py
├── substring-search/
│   └── brute_force.py
├── trees/
│   ├── avl-tree.py
│   ├── binary-heap.py
│   ├── binary-search-tree.py
│   ├── class-representation.py
│   ├── list-representation.py
│   ├── parse-tree.py
│   ├── stack.py
│   └── tree-traversal/
│       ├── functions.py
│       ├── inorder-traversal-example.py
│       ├── postorder-traversal-example.py
│       ├── preorder-traversal-example.py
│       ├── stack.py
│       └── treenode.py
└── trie/
    └── trie.py
Download .txt
SYMBOL INDEX (611 symbols across 110 files)

FILE: analysis/anagrams-linear-solution.py
  function anagrams (line 3) | def anagrams(string1: str, string2: str) -> bool:
  function count_characters (line 23) | def count_characters(string: str, arr: list):

FILE: analysis/anagrams-loglinear-solution.py
  function anagrams (line 3) | def anagrams(string1: str, string2: str) -> bool:

FILE: analysis/anagrams-quadratic-solution.py
  function anagrams (line 3) | def anagrams(string1: str, string2: str) -> bool:

FILE: analysis/time-iterative-approach.py
  function sum_nums (line 4) | def sum_nums(n: int) -> int:

FILE: analysis/time-noniterative-approach.py
  function sum_nums (line 4) | def sum_nums(n: int) -> int:

FILE: deque/circular-deque.py
  class Deque (line 5) | class Deque:
    method __init__ (line 7) | def __init__(self, capacity: int = 10) -> None:
    method is_empty (line 14) | def is_empty(self) -> bool:
    method is_full (line 17) | def is_full(self) -> bool:
    method size (line 20) | def size(self) -> int:
    method add_front (line 23) | def add_front(self, item: Any):
    method add_rear (line 35) | def add_rear(self, item: Any):
    method remove_front (line 45) | def remove_front(self) -> Any:
    method remove_rear (line 56) | def remove_rear(self) -> Any:
    method get_front (line 69) | def get_front(self) -> Any:
    method get_rear (line 74) | def get_rear(self) -> Any:
  function is_palindrome (line 80) | def is_palindrome(string:str='') -> bool:

FILE: deque/deque.py
  class Deque (line 5) | class Deque:
    method __init__ (line 7) | def __init__(self) -> None:
    method is_empty (line 10) | def is_empty(self) -> bool:
    method size (line 13) | def size(self) -> int:
    method add_front (line 16) | def add_front(self, item: Any):
    method add_rear (line 19) | def add_rear(self, item: Any):
    method remove_front (line 22) | def remove_front(self) -> Any:
    method remove_rear (line 27) | def remove_rear(self) -> Any:
  function is_palindrome (line 34) | def is_palindrome(string:str='') -> bool:

FILE: deque/deque_linked_list_impl.py
  class ListNode (line 6) | class ListNode:
    method __init__ (line 8) | def __init__(self, key:Any = None, prev:'ListNode' = None, next:'ListN...
  class Deque (line 14) | class Deque:
    method __init__ (line 16) | def __init__(self):
    method size (line 22) | def size(self) -> int:
    method is_empty (line 26) | def is_empty(self) -> bool:
    method add_front (line 30) | def add_front(self, e:Any) -> None:
    method add_rear (line 39) | def add_rear(self, e:Any) -> None:
    method remove_front (line 48) | def remove_front(self) -> Any:
    method remove_rear (line 61) | def remove_rear(self) -> Any:
    method get_front (line 74) | def get_front(self) -> Any:
    method remove_rear (line 80) | def remove_rear(self) -> Any:

FILE: graphs/bellman-ford-negative-weight-cycle/graph.py
  class Graph (line 10) | class Graph:
    method __init__ (line 11) | def __init__(self):
    method add_vertex (line 17) | def add_vertex(self, label: str):
    method add_edge (line 22) | def add_edge(self, label1: str, label2: str, weight: int):
    method bellman_ford (line 25) | def bellman_ford(self, source: str):
    method print_distances (line 58) | def print_distances(self, source: str):

FILE: graphs/bellman-ford/graph.py
  class Graph (line 10) | class Graph:
    method __init__ (line 13) | def __init__(self) -> None:
    method add_vertex (line 20) | def add_vertex(self, label:str) -> None:
    method add_edge (line 26) | def add_edge(self, v1:str, v2:str, distance:int) -> None:
    method bellman_ford (line 30) | def bellman_ford(self, label:str) -> None:
    method _print_paths (line 50) | def _print_paths(self, label:str) -> None:
    method _return_path (line 60) | def _return_path(self, label:str) -> str:

FILE: graphs/breadth-first-search/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 5) | def __init__(self):
    method add_vertex (line 12) | def add_vertex(self, label: str):
    method add_edge (line 19) | def add_edge(self, label1: str, label2: str):
    method bfs (line 23) | def bfs(self, label: str):
    method return_path (line 37) | def return_path(self, label: str) -> str:

FILE: graphs/breadth-first-search/queue.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self):
    method enqueue (line 5) | def enqueue(self, item):
    method dequeue (line 8) | def dequeue(self):
    method size (line 11) | def size(self):
    method is_empty (line 14) | def is_empty(self):

FILE: graphs/cycle-detection/cycle-directed-graph/graph.py
  class Graph (line 2) | class Graph:
    method __init__ (line 3) | def __init__(self):
    method add_vertex (line 13) | def add_vertex(self, label: str):
    method add_edge (line 20) | def add_edge(self, label1: str, label2: str):
    method dfs (line 23) | def dfs(self, label: str):
    method return_path (line 38) | def return_path(self, label: str) -> str:

FILE: graphs/cycle-detection/cycle-undirected-graph/graph.py
  class Graph (line 2) | class Graph:
    method __init__ (line 3) | def __init__(self):
    method add_vertex (line 13) | def add_vertex(self, label: str):
    method add_edge (line 20) | def add_edge(self, label1: str, label2: str):
    method dfs (line 24) | def dfs(self, label: str):
    method return_path (line 39) | def return_path(self, label: str) -> str:

FILE: graphs/depth-first-search/depth-first-search-recursive/graph.py
  class Graph (line 2) | class Graph:
    method __init__ (line 3) | def __init__(self):
    method add_vertex (line 13) | def add_vertex(self, label: str):
    method add_edge (line 20) | def add_edge(self, label1: str, label2: str):
    method dfs (line 24) | def dfs(self, label: str):
    method return_path (line 37) | def return_path(self, label: str) -> str:

FILE: graphs/depth-first-search/depth-first-search/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 5) | def __init__(self):
    method add_vertex (line 15) | def add_vertex(self, label: str):
    method add_edge (line 22) | def add_edge(self, label1: str, label2: str):
    method dfs (line 26) | def dfs(self, label: str):
    method return_path (line 48) | def return_path(self, label: str) -> str:
    method find_unvisited_neighbour (line 54) | def find_unvisited_neighbour(self, tmp) -> str:

FILE: graphs/depth-first-search/depth-first-search/stack.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self):
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 14) | def peek(self):
    method size (line 17) | def size(self):

FILE: graphs/dijkstra/adjacency-list-impl/main.py
  class Graph (line 7) | class Graph:
    method __init__ (line 9) | def __init__(self, capacity :int =10):
    method add_vertex (line 18) | def add_vertex(self, label :str, weight :int = float('inf')) -> None:
    method add_edge (line 25) | def add_edge(self, label1 :str, label2 :str, weight :int) -> None:
    method dijkstra (line 30) | def dijkstra(self, label :str) -> None:
    method show_path (line 44) | def show_path(self, label :str) -> str:
    method _find_cheapest_vertex (line 50) | def _find_cheapest_vertex(self) -> Vertex:

FILE: graphs/dijkstra/adjacency-list-impl/vertex.py
  class Vertex (line 3) | class Vertex:
    method __init__ (line 5) | def __init__(self, label :str, weight :int = float('inf')):

FILE: graphs/dijkstra/matrix-impl/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 5) | def __init__(self, size: int = 10):
    method add_vertex (line 14) | def add_vertex(self, label: str):
    method add_edge (line 24) | def add_edge(self, label1: str, label2: str, weight: int):
    method dijkstra (line 29) | def dijkstra(self, label: str):
    method return_path (line 43) | def return_path(self, label: str) -> str:
    method find_minimum_weight_vertex (line 49) | def find_minimum_weight_vertex(self):

FILE: graphs/dijkstra/matrix-impl/vertex.py
  class Vertex (line 1) | class Vertex:
    method __init__ (line 2) | def __init__(self, label: str  = None, weight: int = float("inf"), ind...

FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/graph.py
  class Graph (line 5) | class Graph:
    method __init__ (line 6) | def __init__(self):
    method add_vertex (line 11) | def add_vertex(self, label: str):
    method add_edge (line 17) | def add_edge(self, label1: str, label2: str, weight: int):
    method dijkstra (line 20) | def dijkstra(self, label: str):
    method show_path (line 34) | def show_path(self, label: str) -> str:

FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/priorityqueue.py
  class PriorityQueue (line 4) | class PriorityQueue:
    method __init__ (line 5) | def __init__(self):
    method is_empty (line 9) | def is_empty(self) -> bool:
    method insert (line 12) | def insert(self, vertex: Vertex):
    method _perc_up (line 18) | def _perc_up(self, pointer: int):
    method decrease_key (line 26) | def decrease_key(self, pointer: int):
    method delete_min (line 29) | def delete_min(self) -> Vertex:
    method _perc_down (line 40) | def _perc_down(self, pointer: int):
    method _find_swap_index (line 49) | def _find_swap_index(self, pointer: int) -> int:

FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/vertex.py
  class Vertex (line 1) | class Vertex:
    method __init__ (line 2) | def __init__(self, label: str = None, weight: int = float("inf"), key:...

FILE: graphs/is-graph-bipartite/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 5) | def __init__(self):
    method add_vertex (line 10) | def add_vertex(self, label: str = None):
    method add_edge (line 15) | def add_edge(self, label1: str = None, label2: str = None):
    method bipartite_check (line 19) | def bipartite_check(self) -> bool:

FILE: graphs/is-graph-bipartite/queue.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self) -> bool:
    method enqueue (line 8) | def enqueue(self, vertex: str):
    method dequeue (line 11) | def dequeue(self):

FILE: graphs/kosarajus-algorithm/graph.py
  class Graph (line 7) | class Graph:
    method __init__ (line 10) | def __init__(self) -> None:
    method add_vertex (line 18) | def add_vertex(self, label:str) -> None:
    method add_edge (line 24) | def add_edge(self, label1:str, label2:str) -> None:
    method kosaraju (line 31) | def kosaraju(self) -> List[List[str]]:
    method _dfs (line 53) | def _dfs(self, label:str) -> None:
    method _dfs_reversed (line 63) | def _dfs_reversed(self, v:str, connected:List[str]) -> None:

FILE: graphs/kosarajus-algorithm/stack.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self) -> bool:
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 16) | def peek(self):
    method size (line 22) | def size(self) -> int:

FILE: graphs/minimum-spanning-tree/breadth-first-search/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 5) | def __init__(self):
    method add_vertex (line 12) | def add_vertex(self, label: str):
    method add_edge (line 19) | def add_edge(self, label1: str, label2: str):
    method minimum_spanning_tree (line 23) | def minimum_spanning_tree(self, label: str) -> list:  # this is breadt...
    method return_path (line 40) | def return_path(self, label: str) -> str:

FILE: graphs/minimum-spanning-tree/breadth-first-search/queue.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self):
    method enqueue (line 5) | def enqueue(self, item):
    method dequeue (line 8) | def dequeue(self):
    method size (line 11) | def size(self):
    method is_empty (line 14) | def is_empty(self):

FILE: graphs/minimum-spanning-tree/kruskals-algorithm/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 7) | def __init__(self) -> None:
    method add_vertex (line 15) | def add_vertex(self, label:str) -> None:
    method add_edge (line 21) | def add_edge(self, label1:str, label2:str, weight:int) -> None:
    method kruskal (line 27) | def kruskal(self) -> List[Tuple[str, str, int]]:
    method _find_root (line 47) | def _find_root(self, label:str) -> str:

FILE: graphs/minimum-spanning-tree/prims-algorithm/graph.py
  class Graph (line 7) | class Graph:
    method __init__ (line 9) | def __init__(self) -> None:
    method add_vertex (line 15) | def add_vertex(self, label:str, weight:int=float('inf')):
    method add_edge (line 21) | def add_edge(self, label1:str, label2:str, weight:int):
    method prims (line 26) | def prims(self, label:str):

FILE: graphs/minimum-spanning-tree/prims-algorithm/priorityqueue.py
  class PriorityQueue (line 6) | class PriorityQueue:
    method __init__ (line 9) | def __init__(self) -> None:
    method is_empty (line 14) | def is_empty(self) -> bool:
    method insert (line 18) | def insert(self, v:Vertex):
    method perc_up (line 25) | def perc_up(self, index:int):
    method decrease_key (line 34) | def decrease_key(self, key:int):
    method get_min (line 38) | def get_min(self) -> Vertex:
    method delete_min (line 44) | def delete_min(self) -> Vertex:
    method perc_down (line 56) | def perc_down(self, index:int):
    method find_min_index (line 67) | def find_min_index(self, index:int) -> int:

FILE: graphs/minimum-spanning-tree/prims-algorithm/vertex.py
  class Vertex (line 1) | class Vertex:
    method __init__ (line 3) | def __init__(self, label:str=None, weight:int=float('inf'), index:int=...

FILE: graphs/topological-sorting/graph.py
  class Graph (line 1) | class Graph:
    method __init__ (line 2) | def __init__(self):
    method add_vertex (line 14) | def add_vertex(self, label: str):
    method add_edge (line 22) | def add_edge(self, label1: str, label2: str):
    method topsort (line 27) | def topsort(self):
    method dfs (line 34) | def dfs(self, start: str):
    method show_path (line 48) | def show_path(self, label: str) -> str:

FILE: graphs/union-find/number-of-connected-components/graph.py
  function find_parent (line 3) | def find_parent(i: int, components: List[int]) -> int:
  class Graph (line 10) | class Graph:
    method number_of_connected_components (line 11) | def number_of_connected_components(self, edges_matrix: List[List[int]]...

FILE: graphs/union-find/union-find-path-compression/graph.py
  class Graph (line 4) | class Graph:
    method __init__ (line 6) | def __init__(self):
    method add_vertex (line 10) | def add_vertex(self, label: str = None):
    method add_edge (line 13) | def add_edge(self, label1: str, label2: str):
    method union_find (line 16) | def union_find(self):
    method find_root (line 35) | def find_root(self, vertex: Vertex):

FILE: graphs/union-find/union-find-path-compression/vertex.py
  class Vertex (line 1) | class Vertex:
    method __init__ (line 3) | def __init__(self, label: str = None):

FILE: hash-table/chaining.py
  class KeyValue (line 5) | class KeyValue:
    method __init__ (line 7) | def __init__(self, key: int, value: Any) -> None:
  class HashTable (line 12) | class HashTable:
    method __init__ (line 15) | def __init__(self, capacity:int = 11) -> None:
    method put (line 21) | def put(self, key: int, value: Any) -> int:
    method get (line 42) | def get(self, key: int) -> Any:
    method contains (line 61) | def contains(self, key: int) -> bool:
    method delete (line 80) | def delete(self, key: int) -> None:
    method hash (line 102) | def hash(self, key: int) -> int:
    method size (line 106) | def size(self) -> int:

FILE: hash-table/linear-probing.py
  class HashTable (line 6) | class HashTable:
    method __init__ (line 8) | def __init__(self, capacity:int = 11) -> None:
    method put (line 15) | def put(self, key:int, value:Any):
    method contains (line 35) | def contains(self, key:int) -> bool:
    method get (line 40) | def get(self, key:int) -> Any:
    method delete (line 47) | def delete(self, key:int):
    method find (line 56) | def find(self, key:int) -> Tuple[int, bool]:
    method size (line 72) | def size(self) -> int:
    method hash (line 75) | def hash(self, key:int) -> int:
    method rehash (line 79) | def rehash(self, old_hash:int) -> int:

FILE: linked-lists/circular-doubly-linked-list/list.py
  class List (line 4) | class List:
    method __init__ (line 5) | def __init__(self):
    method is_empty (line 9) | def is_empty(self) -> bool:
    method print_all (line 12) | def print_all(self):
    method number_of_elements (line 24) | def number_of_elements(self) -> int:
    method add_to_head (line 37) | def add_to_head(self, key: int):
    method add_to_tail (line 47) | def add_to_tail(self, key: int):
    method delete_from_head (line 57) | def delete_from_head(self) -> int:
    method delete_from_tail (line 70) | def delete_from_tail(self) -> int:
    method delete_nodes_with_value (line 83) | def delete_nodes_with_value(self, key: int):
    method delete_on_index (line 100) | def delete_on_index(self, index: int):
    method insert_after (line 119) | def insert_after(self, list_element: int, new_element: int):
    method insert_before (line 137) | def insert_before(self, list_element: int, new_element: int):
    method sort (line 154) | def sort(self):

FILE: linked-lists/circular-doubly-linked-list/node.py
  class Node (line 1) | class Node:
    method __init__ (line 2) | def __init__(self, key: int=None, prev=None, next=None):

FILE: linked-lists/circular-singly-linked-list/list.py
  class List (line 4) | class List:
    method __init__ (line 5) | def __init__(self):
    method is_empty (line 9) | def is_empty(self) -> bool:
    method print_all (line 12) | def print_all(self):
    method number_of_elements (line 24) | def number_of_elements(self) -> int:
    method add_to_head (line 37) | def add_to_head(self, key: int):
    method add_to_tail (line 45) | def add_to_tail(self, key: int):
    method delete_from_head (line 53) | def delete_from_head(self) -> int:
    method delete_from_tail (line 65) | def delete_from_tail(self) -> int:
    method delete_nodes_with_value (line 80) | def delete_nodes_with_value(self, key: int):
    method delete_on_index (line 96) | def delete_on_index(self, index: int):
    method insert_after (line 116) | def insert_after(self, list_element: int, new_element: int):
    method insert_before (line 132) | def insert_before(self, list_element: int, new_element: int):
    method sort (line 149) | def sort(self):

FILE: linked-lists/circular-singly-linked-list/node.py
  class Node (line 1) | class Node:
    method __init__ (line 2) | def __init__(self, key: int=None, next=None):

FILE: linked-lists/doubly-linked-list/list.py
  class List (line 4) | class List:
    method __init__ (line 5) | def __init__(self):
    method is_empty (line 9) | def is_empty(self) -> bool:
    method print_all (line 12) | def print_all(self):
    method number_of_elements (line 19) | def number_of_elements(self) -> int:
    method add_to_head (line 27) | def add_to_head(self, key: int):
    method add_to_tail (line 34) | def add_to_tail(self, key: int):
    method delete_from_head (line 41) | def delete_from_head(self) -> int:
    method delete_from_tail (line 53) | def delete_from_tail(self) -> int:
    method delete_nodes_with_value (line 65) | def delete_nodes_with_value(self, key: int):
    method delete_on_index (line 83) | def delete_on_index(self, index: int):
    method insert_after (line 102) | def insert_after(self, list_element: int, new_element: int):
    method insert_before (line 115) | def insert_before(self, list_element: int, new_element: int):
    method sort (line 127) | def sort(self):

FILE: linked-lists/doubly-linked-list/node.py
  class Node (line 1) | class Node:
    method __init__ (line 2) | def __init__(self, key: int=None, prev=None, next=None):

FILE: linked-lists/singly-linked-list/list.py
  class List (line 4) | class List:
    method __init__ (line 5) | def __init__(self):
    method is_empty (line 9) | def is_empty(self) -> bool:
    method print_all (line 12) | def print_all(self):
    method number_of_elements (line 19) | def number_of_elements(self) -> int:
    method add_to_head (line 27) | def add_to_head(self, key: int):
    method add_to_tail (line 33) | def add_to_tail(self, key: int):
    method delete_from_head (line 40) | def delete_from_head(self) -> int:
    method delete_from_tail (line 51) | def delete_from_tail(self) -> int:
    method delete_nodes_with_value (line 66) | def delete_nodes_with_value(self, key: int):
    method delete_on_index (line 82) | def delete_on_index(self, index: int):
    method insert_after (line 102) | def insert_after(self, list_element: int, new_element: int):
    method insert_before (line 113) | def insert_before(self, list_element: int, new_element: int):
    method sort (line 125) | def sort(self):

FILE: linked-lists/singly-linked-list/node.py
  class Node (line 1) | class Node:
    method __init__ (line 2) | def __init__(self, key: int=None, next=None):

FILE: queue/circular-queue-fixed-size-array-impl.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self, length: int = 10):
    method enqueue (line 8) | def enqueue(self, item):
    method dequeue (line 17) | def dequeue(self):
    method peek (line 28) | def peek(self):
    method size (line 33) | def size(self) -> int:
    method is_empty (line 36) | def is_empty(self) -> bool:
    method is_full (line 39) | def is_full(self) -> bool:
  function hot_potato (line 43) | def hot_potato(namelist, number):

FILE: queue/queue-array-impl.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self):
    method enqueue (line 5) | def enqueue(self, item):
    method dequeue (line 8) | def dequeue(self):
    method peek (line 13) | def peek(self):
    method size (line 18) | def size(self) -> int:
    method is_empty (line 21) | def is_empty(self) -> bool:
  function hot_potato (line 25) | def hot_potato(namelist, number):

FILE: queue/queue-fixed-size-array-impl.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self, length: int = 10):
    method enqueue (line 8) | def enqueue(self, item):
    method dequeue (line 17) | def dequeue(self):
    method peek (line 28) | def peek(self):
    method size (line 33) | def size(self) -> int:
    method is_empty (line 36) | def is_empty(self) -> bool:
    method is_full (line 39) | def is_full(self) -> bool:
  function hot_potato (line 43) | def hot_potato(namelist, number):

FILE: queue/queue-linked-list-impl.py
  class Node (line 2) | class Node:
    method __init__ (line 3) | def __init__(self, key, next=None):
  class Queue (line 8) | class Queue:
    method __init__ (line 9) | def __init__(self):
    method enqueue (line 13) | def enqueue(self, item):
    method dequeue (line 21) | def dequeue(self):
    method peek (line 32) | def peek(self):
    method size (line 37) | def size(self) -> int:
    method is_empty (line 40) | def is_empty(self) -> bool:
  function hot_potato (line 44) | def hot_potato(namelist, number):

FILE: queue/queue-two-stacks-impl.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self):
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 14) | def peek(self):
    method size (line 17) | def size(self):
  class Queue (line 21) | class Queue:
    method __init__ (line 22) | def __init__(self):
    method is_empty (line 26) | def is_empty(self) -> bool:
    method size (line 29) | def size(self) -> int:
    method enqueue (line 32) | def enqueue(self, item):
    method dequeue (line 35) | def dequeue(self):
    method peek (line 45) | def peek(self):
  function hot_potato (line 57) | def hot_potato(people: list, num: int) -> str:

FILE: recursion/convert-number-iterative.py
  function converter (line 4) | def converter(num: int, base: int) -> str:

FILE: recursion/convert-number.py
  function converter (line 1) | def converter(num: int, base: int) -> str:

FILE: recursion/factorial.py
  function factorial_rec (line 1) | def factorial_rec(n: int) -> int:
  function factorial_it (line 8) | def factorial_it(n: int) -> int:

FILE: recursion/fibonacci-iterative.py
  function fibonacci (line 1) | def fibonacci(n: int) -> int:

FILE: recursion/fibonacci-memoization.py
  function fibonacci (line 5) | def fibonacci(n: int) -> int:

FILE: recursion/fibonacci-recursive-worst-solution.py
  function fibonacci (line 1) | def fibonacci(n: int) -> int:

FILE: recursion/fibonacci-recursive.py
  function fibonacci (line 1) | def fibonacci(n: int) -> int:

FILE: recursion/fibonacci-sum-iterative.py
  function fibonaci (line 1) | def fibonaci(n: int):

FILE: recursion/fibonacci-sum-recursive.py
  function fibonacci (line 1) | def fibonacci(n: int) -> int:

FILE: recursion/maze.py
  class Stack (line 4) | class Stack:
    method __init__ (line 6) | def __init__(self) -> None:
    method is_empty (line 10) | def is_empty(self) -> bool:
    method size (line 14) | def size(self) -> int:
    method push (line 18) | def push(self, item:Any) -> None:
    method peek (line 22) | def peek(self) -> Any:
    method pop (line 28) | def pop(self) -> Any:
  function path_finder (line 57) | def path_finder(matrix:List[List[int]], stack:Stack) -> None:
  function _move (line 61) | def _move(matrix:List[List[int]], stack:Stack) -> None:
  function _add_coordinates_to_stack (line 86) | def _add_coordinates_to_stack(matrix:List[List[int]], stack:Stack, x:int...

FILE: recursion/palindrome.py
  function palindrome_checker (line 1) | def palindrome_checker(string: str) -> bool:
  function palindrome_checker_iterative (line 22) | def palindrome_checker_iterative(string:str) -> bool:
  function palindrome_checker_slicing (line 40) | def palindrome_checker_slicing(string: str) -> bool:

FILE: recursion/reverse-linked-list-iterative-stack.py
  class ListNode (line 3) | class ListNode:
    method __init__ (line 4) | def __init__(self, payload = None, next: 'ListNode' = None) -> None:
  function reverse_list (line 10) | def reverse_list(head: ListNode) -> ListNode:

FILE: recursion/reverse-linked-list-iterative.py
  class ListNode (line 1) | class ListNode:
    method __init__ (line 2) | def __init__(self, val=0, next=None):
  function reverse_list (line 7) | def reverse_list(head: ListNode) -> ListNode:

FILE: recursion/reverse-linked-list.py
  class ListNode (line 1) | class ListNode:
    method __init__ (line 2) | def __init__(self, val: int = None, next = None):
  function reverse_linked_list (line 7) | def reverse_linked_list(node: ListNode) -> ListNode:

FILE: recursion/reverse-list.py
  function reverse_rec (line 4) | def reverse_rec(elements: list):
  function reverse_iterative (line 19) | def reverse_iterative(elements: list):

FILE: recursion/reverse-string.py
  function reverse_string (line 5) | def reverse_string(string: str) -> str:
  function reverse_string_ex_one (line 19) | def reverse_string_ex_one(string: str) -> str:
  function reverse_string_ex_two (line 29) | def reverse_string_ex_two(string: str) -> str:

FILE: recursion/stack.py
  class Stack (line 1) | class Stack:
    method __init__ (line 3) | def __init__(self):
    method is_empty (line 6) | def is_empty(self):
    method push (line 9) | def push(self, item):
    method pop (line 12) | def pop(self):
    method peek (line 15) | def peek(self):
    method size (line 18) | def size(self):

FILE: recursion/sum-numbers-binary-recursion.py
  function sum_numbers (line 5) | def sum_numbers(nums: list) -> int:

FILE: recursion/sum-numbers-pointer.py
  function sum_numbers (line 5) | def sum_numbers(nums: list) -> int:

FILE: recursion/sum-numbers-slicing.py
  function sum_numbers (line 5) | def sum_numbers(nums: list) -> int:

FILE: recursion/towers-of-hanoi.py
  function print_move (line 1) | def print_move(start: str, end: str):
  function towers (line 5) | def towers(number: int, start: str, spare: str, end: str):

FILE: searching/binary-search-iterative.py
  function binary_search (line 2) | def binary_search(nums: list, target: int) -> bool:

FILE: searching/binary-search-recursive-pointers.py
  function binary_search (line 2) | def binary_search(nums: list, target: int) -> bool:

FILE: searching/binary-search-recursive.py
  function binary_search (line 3) | def binary_search(nums: list, target: int) -> bool:

FILE: searching/sequential-search-ordered-list.py
  function sequential_search (line 2) | def sequential_search(nums: list, target: int) -> bool:

FILE: searching/sequential-search-unordered-list.py
  function sequential_search (line 2) | def sequential_search(nums: list, target: int) -> bool:

FILE: sorting/bubble-sort.py
  function bubble_sort (line 5) | def bubble_sort(nums: list):

FILE: sorting/insertion-sort.py
  function insertion_sort (line 5) | def insertion_sort(nums: list):

FILE: sorting/merge-sort.py
  function merge_sort (line 5) | def merge_sort(nums: list):

FILE: sorting/quicksort-return-new-array.py
  function quick_sort (line 4) | def quick_sort(nums: list) -> list:

FILE: sorting/quicksort.py
  function quick_sort (line 4) | def quick_sort(nums: list, i: int, j: int):

FILE: sorting/selection-sort.py
  function selection_sort (line 5) | def selection_sort(nums: list):

FILE: sorting/short-bubble.py
  function short_bubble (line 5) | def short_bubble(nums: list):

FILE: stack/examples/balanced-brackets.py
  function balanced_brackets (line 4) | def balanced_brackets(string: str) -> bool:

FILE: stack/examples/number_converter.py
  function base_converter (line 4) | def base_converter(num, base) -> str:

FILE: stack/examples/stack.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self) -> bool:
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 16) | def peek(self):
    method size (line 22) | def size(self) -> int:

FILE: stack/stack-array-impl-less-efficient.py
  class Stack (line 4) | class Stack:
    method __init__ (line 5) | def __init__(self):
    method is_empty (line 8) | def is_empty(self) -> bool:
    method push (line 11) | def push(self, item):
    method pop (line 14) | def pop(self):
    method peek (line 19) | def peek(self):
    method size (line 24) | def size(self) -> int:
  function reverse_string (line 28) | def reverse_string(s: str) -> str:

FILE: stack/stack-array-impl.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self) -> bool:
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 16) | def peek(self):
    method size (line 22) | def size(self) -> int:
  function reverse_string (line 26) | def reverse_string(s: str) -> str:

FILE: stack/stack-fixed-size-array-impl.py
  class Stack (line 4) | class Stack:
    method __init__ (line 6) | def __init__(self, capacity: int = 1) -> None:
    method size (line 11) | def size(self) -> int:
    method is_empty (line 14) | def is_empty(self) -> bool:
    method is_full (line 17) | def is_full(self) -> bool:
    method push (line 20) | def push(self, item:Any):
    method pop (line 26) | def pop(self) -> Any:
    method peek (line 33) | def peek(self) -> Any:
  function reverse_string (line 39) | def reverse_string(s: str) -> str:

FILE: stack/stack-linked-list-impl.py
  class Node (line 1) | class Node:
    method __init__ (line 2) | def __init__(self, key=None, next=None):
  class Stack (line 7) | class Stack:
    method __init__ (line 8) | def __init__(self):
    method is_empty (line 12) | def is_empty(self) -> bool:
    method push (line 15) | def push(self, item):
    method pop (line 19) | def pop(self):
    method peek (line 27) | def peek(self):
    method size (line 32) | def size(self) -> int:
  function reverse_string (line 36) | def reverse_string(s: str) -> str:

FILE: stack/stack_two_queues.py
  class Queue (line 1) | class Queue:
    method __init__ (line 2) | def __init__(self):
    method enqueue (line 5) | def enqueue(self, item):
    method dequeue (line 8) | def dequeue(self):
    method size (line 13) | def size(self) -> int:
    method is_empty (line 16) | def is_empty(self) -> bool:
    method peek (line 19) | def peek(self) -> int:
  class MyStack (line 24) | class MyStack:
    method __init__ (line 26) | def __init__(self):
    method empty (line 30) | def empty(self) -> bool:
    method push (line 33) | def push(self, item):
    method pop (line 36) | def pop(self):
    method top (line 48) | def top(self):
    method size (line 61) | def size(self) -> int:

FILE: stack_two_queues.py
  class Queue (line 4) | class Queue:
    method __init__ (line 6) | def __init__(self) -> None:
    method size (line 9) | def size(self) -> int:
    method is_empty (line 12) | def is_empty(self) -> bool:
    method enqueue (line 15) | def enqueue(self, item:Any) -> None:
    method dequeue (line 18) | def dequeue(self) -> Any:
  class Stack (line 25) | class Stack:
    method __init__ (line 27) | def __init__(self):
    method push (line 32) | def push(self, x: Any) -> None:
    method pop (line 36) | def pop(self) -> Any:
    method peek (line 51) | def peek(self) -> Any:
    method is_empty (line 66) | def is_empty(self) -> bool:

FILE: substring-search/brute_force.py
  function search (line 6) | def search(text: str, pattern: str) -> int:

FILE: trees/avl-tree.py
  class TreeNode (line 1) | class TreeNode:
    method __init__ (line 2) | def __init__(self, key=None, value=None, parent=None, left=None, right...
    method has_left_child (line 13) | def has_left_child(self) -> bool:
    method has_right_child (line 16) | def has_right_child(self) -> bool:
    method has_both_children (line 19) | def has_both_children(self) -> bool:
    method is_leaf (line 22) | def is_leaf(self) -> bool:
    method is_root (line 25) | def is_root(self) -> bool:
    method has_parent (line 28) | def has_parent(self) -> bool:
    method is_left_child (line 31) | def is_left_child(self) -> bool:
    method is_right_child (line 34) | def is_right_child(self) -> bool:
    method find_min (line 37) | def find_min(self):
    method find_max (line 45) | def find_max(self):
  class AVLTree (line 54) | class AVLTree:
    method __init__ (line 55) | def __init__(self):
    method size (line 59) | def size(self) -> int:
    method is_empty (line 62) | def is_empty(self) -> bool:
    method put (line 65) | def put(self, key, value):
    method _put (line 72) | def _put(self, root: TreeNode, key, value):
    method get (line 90) | def get(self, key) -> TreeNode:
    method _get (line 96) | def _get(self, root: TreeNode, key) -> TreeNode:
    method contains (line 110) | def contains(self, key) -> bool:
    method delete (line 124) | def delete(self, key):
    method find_min (line 182) | def find_min(self) -> TreeNode:
    method find_max (line 190) | def find_max(self) -> TreeNode:
    method _update_balance_factor (line 198) | def _update_balance_factor(self, root: TreeNode):
    method _rebalance (line 215) | def _rebalance(self, root: TreeNode):
    method _rotate_left (line 227) | def _rotate_left(self, root: TreeNode):
    method _rotate_right (line 245) | def _rotate_right(self, root: TreeNode):

FILE: trees/binary-heap.py
  class BinaryHeap (line 3) | class BinaryHeap:
    method __init__ (line 4) | def __init__(self):
    method is_empty (line 8) | def is_empty(self) -> bool:
    method insert (line 11) | def insert(self, item):
    method perc_up (line 16) | def perc_up(self, index: int):
    method get_min (line 22) | def get_min(self):
    method delete_min (line 27) | def delete_min(self):
    method perc_down (line 37) | def perc_down(self, index: int):
    method find_swap_index (line 44) | def find_swap_index(self, index: int) -> int:
    method build_heap (line 53) | def build_heap(self, nums: list):

FILE: trees/binary-search-tree.py
  class TreeNode (line 1) | class TreeNode:
    method __init__ (line 2) | def __init__(self, key=None, value=None, parent=None, left=None, right...
    method has_left_child (line 9) | def has_left_child(self) -> bool:
    method has_right_child (line 12) | def has_right_child(self) -> bool:
    method has_both_children (line 15) | def has_both_children(self) -> bool:
    method is_leaf (line 18) | def is_leaf(self) -> bool:
    method is_root (line 21) | def is_root(self) -> bool:
    method has_parent (line 24) | def has_parent(self) -> bool:
    method is_left_child (line 27) | def is_left_child(self) -> bool:
    method is_right_child (line 32) | def is_right_child(self) -> bool:
    method find_min (line 37) | def find_min(self):
    method find_max (line 45) | def find_max(self):
  class BinarySearchTree (line 54) | class BinarySearchTree:
    method __init__ (line 55) | def __init__(self):
    method size (line 59) | def size(self) -> int:
    method is_empty (line 62) | def is_empty(self) -> bool:
    method put (line 65) | def put(self, key, value):
    method _put (line 72) | def _put(self, root: TreeNode, key, value):
    method get (line 88) | def get(self, key) -> TreeNode:
    method _get (line 94) | def _get(self, root: TreeNode, key) -> TreeNode:
    method contains (line 108) | def contains(self, key) -> bool:
    method delete (line 122) | def delete(self, key):
    method find_min (line 173) | def find_min(self) -> TreeNode:
    method find_max (line 181) | def find_max(self) -> TreeNode:

FILE: trees/class-representation.py
  class TreeNode (line 1) | class TreeNode:
    method __init__ (line 2) | def __init__(self, key=None, left=None, right=None):
    method insert_root_value (line 7) | def insert_root_value(self, key=None):
    method insert_left (line 10) | def insert_left(self, key=None):
    method insert_right (line 13) | def insert_right(self, key=None):
    method get_root_value (line 16) | def get_root_value(self):
    method get_left_child (line 19) | def get_left_child(self):
    method get_right_child (line 22) | def get_right_child(self):
  function build_tree (line 35) | def build_tree() -> TreeNode:
  function print_tree (line 50) | def print_tree(tree: TreeNode):

FILE: trees/list-representation.py
  function create_tree (line 2) | def create_tree(root=None) -> list:
  function insert_root (line 6) | def insert_root(tree: list, root=None):
  function insert_left (line 10) | def insert_left(tree: list, root=None):
  function insert_right (line 15) | def insert_right(tree: list, root=None):
  function get_root (line 20) | def get_root(tree: list):
  function get_left_child (line 24) | def get_left_child(tree: list) -> list:
  function get_right_child (line 28) | def get_right_child(tree: list) -> list:
  function build_tree (line 41) | def build_tree() -> list:

FILE: trees/parse-tree.py
  class TreeNode (line 7) | class TreeNode:
    method __init__ (line 8) | def __init__(self, key=None, left=None, right=None):
    method insert_root_value (line 13) | def insert_root_value(self, key=None):
    method insert_left (line 16) | def insert_left(self, key=None):
    method insert_right (line 19) | def insert_right(self, key=None):
    method get_root_value (line 22) | def get_root_value(self):
    method get_left_child (line 25) | def get_left_child(self):
    method get_right_child (line 28) | def get_right_child(self):
  function tree_parser (line 39) | def tree_parser(s: str) -> TreeNode:
  function evaluate (line 68) | def evaluate(node: TreeNode) -> int:

FILE: trees/stack.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self):
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 14) | def peek(self):
    method size (line 17) | def size(self):

FILE: trees/tree-traversal/functions.py
  function inorder (line 21) | def inorder(tree: TreeNode):
  function preorder (line 28) | def preorder(tree: TreeNode):
  function postorder (line 35) | def postorder(tree: TreeNode):

FILE: trees/tree-traversal/inorder-traversal-example.py
  function tree_parser (line 14) | def tree_parser(s: str) -> TreeNode:
  function inorder (line 43) | def inorder(node: TreeNode) -> str:

FILE: trees/tree-traversal/postorder-traversal-example.py
  function tree_parser (line 14) | def tree_parser(s: str) -> TreeNode:
  function postorder (line 43) | def postorder(node: TreeNode) -> str:

FILE: trees/tree-traversal/preorder-traversal-example.py
  function tree_parser (line 14) | def tree_parser(s: str) -> TreeNode:
  function preorder (line 43) | def preorder(node: TreeNode, space: int = 0):

FILE: trees/tree-traversal/stack.py
  class Stack (line 1) | class Stack:
    method __init__ (line 2) | def __init__(self):
    method is_empty (line 5) | def is_empty(self):
    method push (line 8) | def push(self, item):
    method pop (line 11) | def pop(self):
    method peek (line 14) | def peek(self):
    method size (line 17) | def size(self):

FILE: trees/tree-traversal/treenode.py
  class TreeNode (line 1) | class TreeNode:
    method __init__ (line 2) | def __init__(self, key=None, left=None, right=None):
    method insert_root_value (line 7) | def insert_root_value(self, key=None):
    method insert_left (line 10) | def insert_left(self, key=None):
    method insert_right (line 13) | def insert_right(self, key=None):
    method get_root_value (line 16) | def get_root_value(self):
    method get_left_child (line 19) | def get_left_child(self):
    method get_right_child (line 22) | def get_right_child(self):
    method inorder (line 25) | def inorder(self):
    method preorder (line 32) | def preorder(self):
    method postorder (line 39) | def postorder(self):

FILE: trie/trie.py
  class TrieNode (line 2) | class TrieNode:
    method __init__ (line 3) | def __init__(self, key, parent = None, children: dict = {}):
  class Trie (line 9) | class Trie:
    method __init__ (line 10) | def __init__(self):
    method insert (line 13) | def insert(self, string: str):
    method contains (line 21) | def contains(self, string: str)->bool:
    method delete (line 32) | def delete(self, string: str):
    method prefix (line 48) | def prefix(self, prefix: str)->list:
    method helper (line 61) | def helper(self, node: TrieNode, words: list, currentWord: str):
    method allWords (line 69) | def allWords(self)->list:
    method count (line 74) | def count(self)->int:
    method countHelper (line 77) | def countHelper(self, node: TrieNode)->int:
Condensed preview — 128 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (177K chars).
[
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": "/__pycache__/\n/venv/\n/.idea/"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2018 Ivan Markovic\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 19186,
    "preview": "# Problem-Solving-with-Algorithms-and-Data-Structures-using-Python\n\nI started the project by learning data structures an"
  },
  {
    "path": "analysis/anagrams-linear-solution.py",
    "chars": 726,
    "preview": "\n# O(n)\ndef anagrams(string1: str, string2: str) -> bool:\n    if len(string1) != len(string2):\n        return False\n\n   "
  },
  {
    "path": "analysis/anagrams-loglinear-solution.py",
    "chars": 600,
    "preview": "\n# O(nlogn)\ndef anagrams(string1: str, string2: str) -> bool:\n\n    if len(string1) != len(string2):\n        return False"
  },
  {
    "path": "analysis/anagrams-quadratic-solution.py",
    "chars": 778,
    "preview": "\n# O(n2)\ndef anagrams(string1: str, string2: str) -> bool:\n    if len(string1) != len(string2):\n        return False\n\n  "
  },
  {
    "path": "analysis/time-iterative-approach.py",
    "chars": 309,
    "preview": "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       "
  },
  {
    "path": "analysis/time-noniterative-approach.py",
    "chars": 277,
    "preview": "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("
  },
  {
    "path": "deque/circular-deque.py",
    "chars": 2624,
    "preview": "\nfrom typing import Any, List\n\n\nclass Deque:\n\n    def __init__(self, capacity: int = 10) -> None:\n        self.capacity:"
  },
  {
    "path": "deque/deque.py",
    "chars": 1085,
    "preview": "\nfrom typing import Any, List\n\n\nclass Deque:\n\n    def __init__(self) -> None:\n        self._deque: List[Any] = []\n\n    d"
  },
  {
    "path": "deque/deque_linked_list_impl.py",
    "chars": 2028,
    "preview": "\n\nfrom typing import List, Any\n\n\nclass ListNode:\n\n    def __init__(self, key:Any = None, prev:'ListNode' = None, next:'L"
  },
  {
    "path": "graphs/bellman-ford/graph.py",
    "chars": 2186,
    "preview": "\n#  Bellman-Ford algorithm\n#  Find shortest paths from one vertex,\n#  to all other vertices in weighted graph.\n#  Runtim"
  },
  {
    "path": "graphs/bellman-ford-negative-weight-cycle/graph.py",
    "chars": 2123,
    "preview": "\n#  Bellman-Ford algorithm\n#  Find shortest paths from one vertex,\n#  to all other vertices in weighted graph.\n#  Runtim"
  },
  {
    "path": "graphs/breadth-first-search/graph.py",
    "chars": 1343,
    "preview": "from queue import Queue\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_l"
  },
  {
    "path": "graphs/breadth-first-search/main.py",
    "chars": 485,
    "preview": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor"
  },
  {
    "path": "graphs/breadth-first-search/queue.py",
    "chars": 290,
    "preview": "class Queue:\n    def __init__(self):\n        self.queue = []\n\n    def enqueue(self, item):\n        self.queue.insert(0, "
  },
  {
    "path": "graphs/cycle-detection/Cycle.md",
    "chars": 277,
    "preview": "\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 "
  },
  {
    "path": "graphs/cycle-detection/cycle-directed-graph/graph.py",
    "chars": 1391,
    "preview": "\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        se"
  },
  {
    "path": "graphs/cycle-detection/cycle-directed-graph/main.py",
    "chars": 253,
    "preview": "from graph import Graph\n\ngraph: Graph = Graph()\n\n\nvertices = [\"a\", \"b\", \"c\", \"d\"]\nfor vertex in vertices:\n    graph.add_"
  },
  {
    "path": "graphs/cycle-detection/cycle-undirected-graph/graph.py",
    "chars": 1476,
    "preview": "\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        se"
  },
  {
    "path": "graphs/cycle-detection/cycle-undirected-graph/main.py",
    "chars": 255,
    "preview": "from graph import Graph\n\ngraph: Graph = Graph()\n\n\nvertices = [\"a\", \"b\", \"c\", \"d\"]\nfor vertex in vertices:\n    graph.add_"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search/graph.py",
    "chars": 1842,
    "preview": "from stack import Stack\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_l"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search/main.py",
    "chars": 485,
    "preview": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search/stack.py",
    "chars": 348,
    "preview": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n "
  },
  {
    "path": "graphs/depth-first-search/depth-first-search-recursive/graph.py",
    "chars": 1337,
    "preview": "\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_list: dict = {}\n        se"
  },
  {
    "path": "graphs/depth-first-search/depth-first-search-recursive/main.py",
    "chars": 485,
    "preview": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor"
  },
  {
    "path": "graphs/dijkstra/adjacency-list-impl/main.py",
    "chars": 2190,
    "preview": "\nfrom typing import List, Dict, Set\n\nfrom vertex import Vertex\n\n\nclass Graph:\n\n    def __init__(self, capacity :int =10)"
  },
  {
    "path": "graphs/dijkstra/adjacency-list-impl/vertex.py",
    "chars": 146,
    "preview": "\n\nclass Vertex:\n\n    def __init__(self, label :str, weight :int = float('inf')):\n        self.label :str = label\n       "
  },
  {
    "path": "graphs/dijkstra/matrix-impl/graph.py",
    "chars": 2346,
    "preview": "from vertex import Vertex\n\n\nclass Graph:\n    def __init__(self, size: int = 10):\n        self.size: int = size\n        s"
  },
  {
    "path": "graphs/dijkstra/matrix-impl/main.py",
    "chars": 488,
    "preview": "from graph import Graph\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"START\")\ngraph.add_vertex(\"A\")\ngraph.add_vertex(\"C\")\n"
  },
  {
    "path": "graphs/dijkstra/matrix-impl/vertex.py",
    "chars": 204,
    "preview": "class Vertex:\n    def __init__(self, label: str  = None, weight: int = float(\"inf\"), index: int = None):\n        self.la"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/graph.py",
    "chars": 1362,
    "preview": "from vertex import Vertex\nfrom priorityqueue import PriorityQueue\n\n\nclass Graph:\n    def __init__(self):\n        self._v"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/main.py",
    "chars": 487,
    "preview": "from graph import Graph\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"START\")\ngraph.add_vertex(\"A\")\ngraph.add_vertex(\"C\")\n"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/priorityqueue.py",
    "chars": 1888,
    "preview": "from vertex import Vertex\n\n\nclass PriorityQueue:\n    def __init__(self):\n        self.pq: list = [None]\n        self._po"
  },
  {
    "path": "graphs/dijkstra/priority-queue-impl-adjacency-map/vertex.py",
    "chars": 196,
    "preview": "class Vertex:\n    def __init__(self, label: str = None, weight: int = float(\"inf\"), key: int = None):\n        self.label"
  },
  {
    "path": "graphs/is-graph-bipartite/graph.py",
    "chars": 1303,
    "preview": "from queue import Queue\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_l"
  },
  {
    "path": "graphs/is-graph-bipartite/main.py",
    "chars": 278,
    "preview": "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# "
  },
  {
    "path": "graphs/is-graph-bipartite/queue.py",
    "chars": 342,
    "preview": "class Queue:\n    def __init__(self):\n        self._queue: list = []\n\n    def is_empty(self) -> bool:\n        return len("
  },
  {
    "path": "graphs/kosarajus-algorithm/graph.py",
    "chars": 1873,
    "preview": "\n\nfrom typing import Dict, List, Set\n\nfrom stack import Stack\n\nclass Graph:\n\n\n    def __init__(self) -> None:\n        se"
  },
  {
    "path": "graphs/kosarajus-algorithm/main.py",
    "chars": 320,
    "preview": "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"
  },
  {
    "path": "graphs/kosarajus-algorithm/stack.py",
    "chars": 574,
    "preview": "class Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len("
  },
  {
    "path": "graphs/minimum-spanning-tree/breadth-first-search/graph.py",
    "chars": 1510,
    "preview": "from queue import Queue\n\n\nclass Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacency_l"
  },
  {
    "path": "graphs/minimum-spanning-tree/breadth-first-search/main.py",
    "chars": 530,
    "preview": "from graph import Graph\n\ngraph = Graph()\n\nmy_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']\n# add vertices\nfor"
  },
  {
    "path": "graphs/minimum-spanning-tree/breadth-first-search/queue.py",
    "chars": 290,
    "preview": "class Queue:\n    def __init__(self):\n        self.queue = []\n\n    def enqueue(self, item):\n        self.queue.insert(0, "
  },
  {
    "path": "graphs/minimum-spanning-tree/kruskals-algorithm/graph.py",
    "chars": 1601,
    "preview": "\nfrom typing import List, Set, Dict, Tuple\n\nclass Graph:\n\n\n    def __init__(self) -> None:\n        self.vertices:Set[str"
  },
  {
    "path": "graphs/minimum-spanning-tree/kruskals-algorithm/main.py",
    "chars": 347,
    "preview": "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.a"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/graph.py",
    "chars": 1500,
    "preview": "\nfrom typing import Dict, List\n\nfrom vertex import Vertex\nfrom priorityqueue import PriorityQueue\n\nclass Graph:\n\n    def"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/main.py",
    "chars": 467,
    "preview": "from graph import Graph\n\n\ngraph: Graph = Graph()\n\ngraph.add_vertex(\"a\")\ngraph.add_vertex(\"b\")\ngraph.add_vertex(\"f\")\ngrap"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/priorityqueue.py",
    "chars": 2067,
    "preview": "from typing import List\n\nfrom vertex import Vertex\n\n\nclass PriorityQueue:\n\n\n    def __init__(self) -> None:\n        self"
  },
  {
    "path": "graphs/minimum-spanning-tree/prims-algorithm/vertex.py",
    "chars": 200,
    "preview": "class Vertex:\n\n    def __init__(self, label:str=None, weight:int=float('inf'), index:int=None) -> None:\n        self.lab"
  },
  {
    "path": "graphs/topological-sorting/graph.py",
    "chars": 1858,
    "preview": "class Graph:\n    def __init__(self):\n        self.vertices: list = []\n        self.adjacencyList: dict = {}\n        self"
  },
  {
    "path": "graphs/topological-sorting/main.py",
    "chars": 282,
    "preview": "from graph import Graph\n\ngraph = Graph()\n\nvertices = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"]\nfor vertex in vertices:\n    graph.ad"
  },
  {
    "path": "graphs/union-find/number-of-connected-components/graph.py",
    "chars": 1014,
    "preview": "from typing import List\n\ndef find_parent(i: int, components: List[int]) -> int:\n    \n    while i != components[i]:\n     "
  },
  {
    "path": "graphs/union-find/union-find-path-compression/graph.py",
    "chars": 1176,
    "preview": "from vertex import Vertex\n\n\nclass Graph:\n\n    def __init__(self):\n        self.vertices: dict = {}\n        self.edges: l"
  },
  {
    "path": "graphs/union-find/union-find-path-compression/main.py",
    "chars": 460,
    "preview": "from graph import Graph\nfrom vertex import Vertex\n\ng: Graph = Graph()\ng.add_vertex('a')\ng.add_vertex('b')\ng.add_vertex('"
  },
  {
    "path": "graphs/union-find/union-find-path-compression/vertex.py",
    "chars": 150,
    "preview": "class Vertex:\n\n    def __init__(self, label: str = None):\n        self.label = label\n        self.parent: 'Vertex' = sel"
  },
  {
    "path": "hash-table/chaining.py",
    "chars": 3488,
    "preview": "\n\nfrom typing import Any, List\n\nclass KeyValue:\n\n    def __init__(self, key: int, value: Any) -> None:\n        self.key:"
  },
  {
    "path": "hash-table/linear-probing.py",
    "chars": 3204,
    "preview": "\n\nfrom typing import Any, List, Tuple\n\n\nclass HashTable:\n\n    def __init__(self, capacity:int = 11) -> None:\n        sel"
  },
  {
    "path": "linked-lists/circular-doubly-linked-list/list.py",
    "chars": 5107,
    "preview": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = Non"
  },
  {
    "path": "linked-lists/circular-doubly-linked-list/node.py",
    "chars": 147,
    "preview": "class Node:\n    def __init__(self, key: int=None, prev=None, next=None):\n        self.key = key\n        self.prev = prev"
  },
  {
    "path": "linked-lists/circular-singly-linked-list/list.py",
    "chars": 4721,
    "preview": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = Non"
  },
  {
    "path": "linked-lists/circular-singly-linked-list/node.py",
    "chars": 111,
    "preview": "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",
    "chars": 4268,
    "preview": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = Non"
  },
  {
    "path": "linked-lists/doubly-linked-list/node.py",
    "chars": 147,
    "preview": "class Node:\n    def __init__(self, key: int=None, prev=None, next=None):\n        self.key = key\n        self.prev = prev"
  },
  {
    "path": "linked-lists/singly-linked-list/list.py",
    "chars": 3993,
    "preview": "from node import Node\n\n\nclass List:\n    def __init__(self):\n        self.head: Node = None\n        self.tail: Node = Non"
  },
  {
    "path": "linked-lists/singly-linked-list/node.py",
    "chars": 111,
    "preview": "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",
    "chars": 1498,
    "preview": "class Queue:\n    def __init__(self, length: int = 10):\n        self._length: int = length\n        self._queue: list = [N"
  },
  {
    "path": "queue/queue-array-impl.py",
    "chars": 847,
    "preview": "class Queue:\n    def __init__(self):\n        self._queue = []\n\n    def enqueue(self, item):\n        self._queue.insert(0"
  },
  {
    "path": "queue/queue-fixed-size-array-impl.py",
    "chars": 1424,
    "preview": "class Queue:\n    def __init__(self, length: int = 10):\n        self._length: int = length\n        self._queue: list = [N"
  },
  {
    "path": "queue/queue-linked-list-impl.py",
    "chars": 1330,
    "preview": "\nclass Node:\n    def __init__(self, key, next=None):\n        self.key = key\n        self.next = next\n\n\nclass Queue:\n    "
  },
  {
    "path": "queue/queue-two-stacks-impl.py",
    "chars": 1812,
    "preview": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n "
  },
  {
    "path": "recursion/convert-number-iterative.py",
    "chars": 550,
    "preview": "from stack import Stack\n\n\ndef converter(num: int, base: int) -> str:\n    digits = \"0123456789ABCDEF\"\n    stack: Stack = "
  },
  {
    "path": "recursion/convert-number.py",
    "chars": 230,
    "preview": "def converter(num: int, base: int) -> str:\n    digits = \"0123456789ABCDEF\"\n    if num < base:\n        return digits[num]"
  },
  {
    "path": "recursion/factorial.py",
    "chars": 273,
    "preview": "def factorial_rec(n: int) -> int:\n    if n <= 1:\n        return 1\n    else:\n        return n * factorial_rec(n - 1)\n\n\nde"
  },
  {
    "path": "recursion/fibonacci-iterative.py",
    "chars": 259,
    "preview": "def fibonacci(n: int) -> int:\n    if n <= 1:\n        return 0\n    elif n == 2:\n        return 1\n    prev: int = 0\n    cu"
  },
  {
    "path": "recursion/fibonacci-memoization.py",
    "chars": 296,
    "preview": "\nnums: dict = {}\n\n\ndef fibonacci(n: int) -> int:\n    if n <= 1:\n        return 0\n    elif n == 2:\n        return 1\n    e"
  },
  {
    "path": "recursion/fibonacci-recursive-worst-solution.py",
    "chars": 202,
    "preview": "def fibonacci(n: int) -> int:\n    if n <= 1:\n        return 0\n    elif n == 2:\n        return 1\n    else:\n        return"
  },
  {
    "path": "recursion/fibonacci-recursive.py",
    "chars": 351,
    "preview": "def fibonacci(n: int) -> int:\n\n    def fibonacci_helper(num: int, prev: int = 0, curr: int = 1) -> int:\n        if num <"
  },
  {
    "path": "recursion/fibonacci-sum-iterative.py",
    "chars": 317,
    "preview": "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: "
  },
  {
    "path": "recursion/fibonacci-sum-recursive.py",
    "chars": 376,
    "preview": "def fibonacci(n: int) -> int:\n\n    def fibonacci_helper(num: int, acc: int = 0, prev: int = 0, curr: int = 1):\n        i"
  },
  {
    "path": "recursion/maze.py",
    "chars": 2188,
    "preview": "\nfrom typing import List, Any\n\nclass Stack:\n\n    def __init__(self) -> None:\n        self.stack:List[Any] = list()\n\n\n   "
  },
  {
    "path": "recursion/palindrome.py",
    "chars": 1215,
    "preview": "def palindrome_checker(string: str) -> bool:\n\n    def palindrome_helper(s: str, start: int, end: int) -> bool:\n        i"
  },
  {
    "path": "recursion/reverse-linked-list-iterative-stack.py",
    "chars": 617,
    "preview": "from stack import Stack\n\nclass ListNode:\n    def __init__(self, payload = None, next: 'ListNode' = None) -> None:\n      "
  },
  {
    "path": "recursion/reverse-linked-list-iterative.py",
    "chars": 496,
    "preview": "class ListNode:\n    def __init__(self, val=0, next=None):\n        self.val = val\n        self.next = next\n\n\ndef reverse_"
  },
  {
    "path": "recursion/reverse-linked-list.py",
    "chars": 444,
    "preview": "class ListNode:\n    def __init__(self, val: int = None, next = None):\n        self.val = val\n        self.next = next\n\n\n"
  },
  {
    "path": "recursion/reverse-list.py",
    "chars": 645,
    "preview": "nums: list = [1, 2, 3, 4, 5]\n\n\ndef reverse_rec(elements: list):\n\n    def reverse_list_helper(values: list, start: int, e"
  },
  {
    "path": "recursion/reverse-string.py",
    "chars": 743,
    "preview": "\nstring: str = \"This string will be reversed\"\n\n\ndef reverse_string(string: str) -> str:\n\n    def helper(s: str, end: int"
  },
  {
    "path": "recursion/stack.py",
    "chars": 349,
    "preview": "class Stack:\n\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n"
  },
  {
    "path": "recursion/sum-numbers-binary-recursion.py",
    "chars": 435,
    "preview": "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:"
  },
  {
    "path": "recursion/sum-numbers-pointer.py",
    "chars": 318,
    "preview": "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: i"
  },
  {
    "path": "recursion/sum-numbers-slicing.py",
    "chars": 248,
    "preview": "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:"
  },
  {
    "path": "recursion/towers-of-hanoi.py",
    "chars": 369,
    "preview": "def print_move(start: str, end: str):\n    print(\"Moving from\", start, \"to\", end)\n\n\ndef towers(number: int, start: str, s"
  },
  {
    "path": "searching/binary-search-iterative.py",
    "chars": 552,
    "preview": "\ndef binary_search(nums: list, target: int) -> bool:\n    start: int = 0\n    end: int = len(nums) - 1\n    found: bool = F"
  },
  {
    "path": "searching/binary-search-recursive-pointers.py",
    "chars": 763,
    "preview": "\ndef binary_search(nums: list, target: int) -> bool:\n\n    def binary_search_helper(numbers: list, element, start: int, e"
  },
  {
    "path": "searching/binary-search-recursive.py",
    "chars": 593,
    "preview": "# slicing a list is O(k)\n# better is recursive solution with pointers\ndef binary_search(nums: list, target: int) -> bool"
  },
  {
    "path": "searching/sequential-search-ordered-list.py",
    "chars": 491,
    "preview": "\ndef sequential_search(nums: list, target: int) -> bool:\n    found: bool = False\n    stopped: bool = False\n    i: int = "
  },
  {
    "path": "searching/sequential-search-unordered-list.py",
    "chars": 363,
    "preview": "\ndef sequential_search(nums: list, target: int) -> bool:\n    i: int = 0\n    found: bool = False\n    while i < len(nums) "
  },
  {
    "path": "sorting/bubble-sort.py",
    "chars": 299,
    "preview": "\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 "
  },
  {
    "path": "sorting/insertion-sort.py",
    "chars": 349,
    "preview": "\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  "
  },
  {
    "path": "sorting/merge-sort.py",
    "chars": 689,
    "preview": "\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    mid"
  },
  {
    "path": "sorting/quicksort-return-new-array.py",
    "chars": 575,
    "preview": "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 "
  },
  {
    "path": "sorting/quicksort.py",
    "chars": 898,
    "preview": "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: i"
  },
  {
    "path": "sorting/selection-sort.py",
    "chars": 408,
    "preview": "\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  "
  },
  {
    "path": "sorting/short-bubble.py",
    "chars": 398,
    "preview": "\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"
  },
  {
    "path": "stack/examples/balanced-brackets.py",
    "chars": 628,
    "preview": "from stack import Stack\n\n\ndef balanced_brackets(string: str) -> bool:\n    stack: Stack = Stack()\n    for character in st"
  },
  {
    "path": "stack/examples/number_converter.py",
    "chars": 421,
    "preview": "from stack import Stack\n\n\ndef base_converter(num, base) -> str:\n    digits = \"0123456789ABCDEF\"\n    stack: Stack = Stack"
  },
  {
    "path": "stack/examples/stack.py",
    "chars": 575,
    "preview": "class Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len("
  },
  {
    "path": "stack/stack-array-impl-less-efficient.py",
    "chars": 1019,
    "preview": "# this solution is less efficient because\n# pop() from end of array is faster than pop(some_other_index)\n# append(item) "
  },
  {
    "path": "stack/stack-array-impl.py",
    "chars": 914,
    "preview": "class Stack:\n    def __init__(self):\n        self._stack: list = []\n\n    def is_empty(self) -> bool:\n        return len("
  },
  {
    "path": "stack/stack-fixed-size-array-impl.py",
    "chars": 1271,
    "preview": "from typing import Any, List\n\n\nclass Stack:\n\n    def __init__(self, capacity: int = 1) -> None:\n        self.capacity: i"
  },
  {
    "path": "stack/stack-linked-list-impl.py",
    "chars": 1108,
    "preview": "class Node:\n    def __init__(self, key=None, next=None):\n        self.key = key\n        self.next = next\n\n\nclass Stack:\n"
  },
  {
    "path": "stack/stack_two_queues.py",
    "chars": 1590,
    "preview": "class Queue:\n    def __init__(self):\n        self._queue = []\n\n    def enqueue(self, item):\n        self._queue.insert(0"
  },
  {
    "path": "stack_two_queues.py",
    "chars": 1545,
    "preview": "from typing import Any, List\n\n\nclass Queue:\n\n    def __init__(self) -> None:\n        self.queue:List[Any] = []\n\n    def "
  },
  {
    "path": "substring-search/brute_force.py",
    "chars": 472,
    "preview": "\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"
  },
  {
    "path": "trees/avl-tree.py",
    "chars": 9015,
    "preview": "class TreeNode:\n    def __init__(self, key=None, value=None, parent=None, left=None, right=None,\n                 left_s"
  },
  {
    "path": "trees/binary-heap.py",
    "chars": 1844,
    "preview": "\n# min heap\nclass BinaryHeap:\n    def __init__(self):\n        self.pointer: int = 0\n        self.heap: list = [None]\n\n  "
  },
  {
    "path": "trees/binary-search-tree.py",
    "chars": 5927,
    "preview": "class TreeNode:\n    def __init__(self, key=None, value=None, parent=None, left=None, right=None):\n        self.key = key"
  },
  {
    "path": "trees/class-representation.py",
    "chars": 1359,
    "preview": "class TreeNode:\n    def __init__(self, key=None, left=None, right=None):\n        self.key = key\n        self.left = left"
  },
  {
    "path": "trees/list-representation.py",
    "chars": 1171,
    "preview": "\ndef create_tree(root=None) -> list:\n    return [None, [], []]\n\n\ndef insert_root(tree: list, root=None):\n    tree[0] = r"
  },
  {
    "path": "trees/parse-tree.py",
    "chars": 1933,
    "preview": "\nfrom stack import Stack\nimport operator\nimport re\n\n\nclass TreeNode:\n    def __init__(self, key=None, left=None, right=N"
  },
  {
    "path": "trees/stack.py",
    "chars": 349,
    "preview": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n "
  },
  {
    "path": "trees/tree-traversal/functions.py",
    "chars": 884,
    "preview": "from treenode import TreeNode\n\nnode: TreeNode = TreeNode('a')\nnode.insert_left('b')\nnode.insert_left('c')\nnode.get_left_"
  },
  {
    "path": "trees/tree-traversal/inorder-traversal-example.py",
    "chars": 1447,
    "preview": "\nfrom stack import Stack\nimport operator\nimport re\nfrom treenode import TreeNode\n\nopers = {'+':operator.add, '-':operato"
  },
  {
    "path": "trees/tree-traversal/postorder-traversal-example.py",
    "chars": 1447,
    "preview": "\nfrom stack import Stack\nimport operator\nimport re\nfrom treenode import TreeNode\n\nopers = {'+':operator.add, '-':operato"
  },
  {
    "path": "trees/tree-traversal/preorder-traversal-example.py",
    "chars": 1310,
    "preview": "\nfrom stack import Stack\nimport operator\nimport re\nfrom treenode import TreeNode\n\nopers = {'+':operator.add, '-':operato"
  },
  {
    "path": "trees/tree-traversal/stack.py",
    "chars": 349,
    "preview": "class Stack:\n    def __init__(self):\n        self.items = []\n\n    def is_empty(self):\n        return self.items == []\n\n "
  },
  {
    "path": "trees/tree-traversal/treenode.py",
    "chars": 1288,
    "preview": "class TreeNode:\n    def __init__(self, key=None, left=None, right=None):\n        self.key = key\n        self.left = left"
  },
  {
    "path": "trie/trie.py",
    "chars": 3549,
    "preview": "\r\nclass TrieNode:\r\n    def __init__(self, key, parent = None, children: dict = {}):\r\n        self.key = key\r\n        sel"
  }
]

About this extraction

This page contains the full source code of the ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 128 files (157.9 KB), approximately 43.4k tokens, and a symbol index with 611 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!