├── .gitignore ├── stacks.py ├── queue.py ├── binary_search.py ├── recursion.py ├── bubble_sort.py ├── selection_sort.py ├── README.md ├── merge_sort.py ├── important_notes.txt ├── list_methods.py ├── dictionary_methods.py └── linked_list.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Specify filepatterns you want git to ignore. 2 | 3 | __pycache__ 4 | milestone.txt 5 | -------------------------------------------------------------------------------- /stacks.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Stack abstract data structure.""" 2 | 3 | stack = [3, 4, 5] 4 | 5 | # Time complexity O(1) 6 | stack.append(1) 7 | 8 | # Time complexity O(1) 9 | stack.pop() 10 | -------------------------------------------------------------------------------- /queue.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Queue abstract data structure.""" 2 | 3 | from collections import deque 4 | 5 | 6 | queue = deque(['A', 'B', 'C']) 7 | 8 | 9 | # Add at first. 10 | # Time complexity O(1). 11 | queue.appendleft(-1) 12 | 13 | # Add at end. 14 | # Time complexity O(1). 15 | queue.append('Z') 16 | 17 | # Remove first element. 18 | # Time complexity O(1). 19 | queue.popleft() 20 | 21 | # Remove last element. 22 | # Time complexity O(1). 23 | queue.pop() 24 | -------------------------------------------------------------------------------- /binary_search.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Binary Search.""" 2 | 3 | list_data = [2, 3, 4, 7, 8, 9, 11, 13, 98] 4 | target = 98 5 | 6 | 7 | def binary_search(list_data=list_data, target=target): 8 | """ Time Complexity O(log n)""" 9 | 10 | # Set low and high pointers 11 | low = 0 12 | high = len(list_data) - 1 13 | 14 | while low <= high: 15 | mid = (low + high) // 2 16 | 17 | if list_data[mid] == target: 18 | return mid 19 | elif target < list_data[mid]: 20 | high = mid - 1 21 | else: 22 | low = mid + 1 23 | 24 | # If element does not exist. 25 | return -1 26 | -------------------------------------------------------------------------------- /recursion.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Recursion abstract data structure.""" 2 | 3 | 4 | def recursive(input): 5 | """ Apply n of steps corresponding to the argument input. 6 | Replace return with print to output the steps. 7 | Time Complexity O(n)""" 8 | 9 | # Base Case: Argument input greater than 0. 10 | if input <= 0: 11 | return 0 12 | else: 13 | output = recursive(input - 1) 14 | print(output) 15 | 16 | 17 | def get_fib(position): 18 | """A recursive function that represents the Fibonacci Sequence, takes 19 | position and return its value within the Fibonacci Sequence. 20 | Time Complexity O(n)""" 21 | 22 | # Base Case: Positions greater thatn 0 or 1, since Fibonacci for 0 is 0 and 23 | # 1 is 1. 24 | if position == 0 or position == 1: 25 | return position 26 | 27 | return get_fib(position - 1) + get_fib(position - 2) 28 | -------------------------------------------------------------------------------- /bubble_sort.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Bubble Sort.""" 2 | 3 | 4 | def bubble_sort(arr): 5 | """ Time Complexity O(n ** 2) 6 | Speace Complexity O(1)""" 7 | 8 | n = len(arr) 9 | 10 | # NOTE: Not mandatory for Bubble Sort, will be used within the code for the 11 | # feedback messages to show the steps during execustion, Thus deleting will 12 | # cause ERROR. 13 | outer_loop_result = 0 14 | inner_loop_result = 0 15 | 16 | # Outer loop, loop over all elements in the array. 17 | for outer_loop in range(n): 18 | outer_loop_result += 1 19 | inner_loop_result = 0 20 | print(f"Result: {outer_loop_result} ==> {arr}") 21 | 22 | # Inner loop. 23 | # Loop is (n - 2) to avoid index out of range when swap last two elements. 24 | for inner_loop in range(n - outer_loop - 1): 25 | # If left element greater than its next element, swap them. 26 | if arr[inner_loop] > arr[inner_loop + 1]: 27 | arr[inner_loop], 28 | arr[inner_loop + 1] = arr[inner_loop + 1], arr[inner_loop] 29 | 30 | # Feedback message. 31 | inner_loop_result += 1 32 | print(f" Inner: {inner_loop_result} ==> {arr}") 33 | 34 | 35 | arr = [5, 3, 1, 9, 8, 2, 4, 7] 36 | 37 | bubble_sort(arr) 38 | 39 | print((5 * '\n') + "Sorted array is:") 40 | for i in arr: 41 | print(i) 42 | -------------------------------------------------------------------------------- /selection_sort.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Selection Sort.""" 2 | 3 | 4 | def selection_sort(arr): 5 | """ Time Complexity O(n ** 2). 6 | Space Complexity O(1).""" 7 | 8 | n = len(arr) 9 | 10 | # Feedback messages are not mandatory for Bubble Sort, will be used within 11 | # the code to show the steps during execustion, Thus deleting next variable 12 | # will cause an ERROR. 13 | outer_loop_result = 0 14 | inner_loop_result = 0 15 | 16 | # Outer loop, loop over all elements in the array. 17 | for outer_loop in range(n): 18 | 19 | # Feedback message. 20 | outer_loop_result += 1 21 | print(f"Result: {outer_loop_result} ==> {arr}") 22 | 23 | # Set first index in the array as smallest. 24 | smallest_index = outer_loop 25 | 26 | # Shift by one step for each loop corresponding to the outer loop. 27 | for inner_loop in range(smallest_index + 1, n): 28 | if arr[smallest_index] > arr[inner_loop]: 29 | smallest_index = inner_loop 30 | 31 | # Feedback message. 32 | inner_loop_result += 1 33 | print(f" Inner: {inner_loop_result} ==> {arr}") 34 | 35 | # Swamp elements, if needed. 36 | arr[outer_loop], arr[smallest_index] = arr[smallest_index], arr[outer_loop] 37 | 38 | 39 | arr = [6, 5, 1, 2, 4, 3] 40 | 41 | selection_sort(arr) 42 | 43 | print((5 * '\n') + "Sorted array is:") 44 | for i in arr: 45 | print(i) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Structure and Algorithms with Python 2 | 3 | **Note**: Please consider that, I created this repository from a long time so you may find some bugs on execution, if face a bug, leave a comment or pull a request and I will help. 4 | 5 | This repository is related to the Arabic tutorial [here](https://www.facebook.com/MohamedAymanHassen/posts/1340680682935215), within the tutorial we discuss the common data structure and algorithms and their worst and best case for each, then implement the code using Python. 6 | 7 | Also, note that the Python version used in the implementation is Python3.6, you may find with the latest Python version there is a better way to implement the Data Structure and Algorithms. 8 | 9 | **Please consider to Star the repository** 10 | Although I'm not the best Python developer out there, I put much effort and time into making a good reference as much as possible, staring the repository will leave a good vibe. 11 | 12 | The data structure and algorithms implemented with Python are: 13 | * [Linked List](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/linked_list.py) 14 | * [Stacks](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/stacks.py) 15 | * [Queue](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/queue.py) 16 | * [Recursion](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/recursion.py) 17 | * [Binary Search](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/binary_search.py) 18 | * [Bubble Sort](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/bubble_sort.py) 19 | * [Selection Sort](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/selection_sort.py) 20 | * [Merge Sort](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/merge_sort.py) 21 | 22 | Python built-in data structure methods efficiency. 23 | * [List](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/list_methods.py) 24 | * [Dictionary](https://github.com/mohamedayman28/data_structure_and_algorithms/blob/main/dictionary_methods.py) 25 | -------------------------------------------------------------------------------- /merge_sort.py: -------------------------------------------------------------------------------- 1 | """ Python program for implementation of Merge Sort. 2 | Code originally from: 3 | https://www.geeksforgeeks.org/merge-sort/""" 4 | 5 | 6 | def merge_sort(arr): 7 | """ Divide and Conquer sorting algorithm using recursion abstraction. 8 | Time Complexity O(n log n). 9 | Space Complexity O(n).""" 10 | 11 | # Base Case: Array has more than one element. 12 | if len(arr) > 1: 13 | 14 | # Middle must not be float number . 15 | mid = len(arr) // 2 16 | 17 | # Dividing array into 2 halves. On Prime numbers right half will always 18 | # has greater number of elements by one element. 19 | left_half = arr[:mid] 20 | right_half = arr[mid:] 21 | 22 | print(f"Current array is: {arr}") 23 | print(f" Left of the array: {left_half}") 24 | print(f" Right of the array: {right_half} \n") 25 | 26 | # Each function will keep recurse until array has one element left. 27 | merge_sort(left_half) 28 | merge_sort(right_half) 29 | 30 | i = j = k = 0 31 | 32 | # Copy data to two temporarily arrays left_half[] and right_half[] 33 | while i < len(left_half) and j < len(right_half): 34 | if left_half[i] < right_half[j]: 35 | arr[k] = left_half[i] 36 | i += 1 37 | else: 38 | arr[k] = right_half[j] 39 | j += 1 40 | k += 1 41 | 42 | # Eventually the above loop will break due the imbalance 43 | # e.g. False and True, and biggest number from either left or right half 44 | # will be remained without being added to the temporarily array. 45 | while i < len(left_half): 46 | arr[k] = left_half[i] 47 | i += 1 48 | k += 1 49 | 50 | while j < len(right_half): 51 | arr[k] = right_half[j] 52 | j += 1 53 | k += 1 54 | 55 | 56 | def printList(arr): 57 | for i in range(len(arr)): 58 | print(arr[i], end=" ") 59 | print() 60 | 61 | 62 | arr = [8, 7, 4, 3, 9, 2, 1] 63 | 64 | merge_sort(arr) 65 | print("Sorted array is: ", end="\n") 66 | printList(arr) 67 | -------------------------------------------------------------------------------- /important_notes.txt: -------------------------------------------------------------------------------- 1 | Summary: 2 | This will be an ongoing growing notes picked from the internet along with my experince, 3 | serving the purpose of writing better Python code. 4 | 5 | Notes about: 6 | 1 - Time Complexity from good to worst(top-to-down). >>> line 11 7 | 2 - len(list) is O(1). >>> line 18 8 | 3 - List VS Tuple efficiency. >>> line 22 9 | 4 - Duplicated keys:values pairs in dictionary. >>> line 37 10 | 5 - Dictinary .items(), .keys(), and .values() returns a view object. >>> line 52 11 | 12 | NOTE no.1 Time Complexity from good to worst(top-to-down) 13 | * O(1) Constant time complexity. 14 | * O(log n) Logarithmic time complexity. 15 | * O(n) Linear time complexity. 16 | * O(n log n) Linearithmic time complexity. 17 | * O(n ** 2) Quadratic time complexity. 18 | 19 | NOTE no.2 len(list) is O(1) 20 | * List built on the array data structure 21 | * Array length = (Array_head memory allocation) - (Array_tail memory allocation) 22 | 23 | NOTE no.3 List VS Tuple efficiency. 24 | * Tuple is slightly faster to access than list. 25 | * Tuples are immutable data types which are stored in a single block of memory 26 | so it doesn’t require extra space to store new objects. 27 | * But list are mutable data types and are allocated in two blocks where the 28 | fixed one with all object information and a variable sized block for the data. 29 | * There is slight difference in indexing speed of list and tuple because tuples 30 | uses fewer pointers when indexing comparing to list. Because of fewer pointers, 31 | access mechanism is generally faster in tuples than lists. 32 | * Because that Tuple is an immutable data type, unpacking must be equal to the 33 | variables, otherwise ERROR will occur. 34 | >>>> t = (1, 2, 3, 4) 35 | >>>> s1, s2, s3, s4 = t 36 | While it's OK with list. 37 | 38 | NOTE no.4 Duplicated keys and values in dictionary. 39 | * Create Dictionary with duplicated keys, will neglect first pairs and keep the last. 40 | >>>> {2:'a', 3:'b', 2: 'c'} returns {2: 'c', 3: 'b'} 41 | * Python dictionaries built-on the hash tables data structure. 42 | * Hash Table use the Set abstract data structure to register its keys to the array. 43 | * So we can't have repeated key but it's fine for the repeated values. 44 | >>>> {'a': 10, 'b': 20, 'c': 30} returns {'a': 10, 'b': 20, 'c': 30} 45 | * Since Set has no order, you can't have same result with method like 46 | .popitem() with that being said, the next note has been mentioned on 47 | RealPython website "In Python versions less than 3.6, popitem() would return an arbitrary 48 | (random) key-value pair since Python dictionaries were unordered before version 3.6" 49 | Link: https://realpython.com/python-dicts/#built-in-dictionary-methods. 50 | 51 | 52 | NOTE no.5 Dictionary .items(), .keys(), and .values() returns a view object. 53 | * The .items(), .keys(), and .values() methods actually return something 54 | called a view object. A dictionary view object is more or less like a window 55 | on the keys and values. For practical purposes, you can think of these 56 | methods as returning lists of the dictionary’s keys and values. 57 | * With the previous note being said, view object is not actually a Python list 58 | means we can't index it. 59 | * Window is just a way of peek at the Key:Value pairs of the dictionary. 60 | -------------------------------------------------------------------------------- /list_methods.py: -------------------------------------------------------------------------------- 1 | """ Script is about: 2 | Efficiency cheat-sheet for Python list data structure. More details at 3 | https://wiki.python.org/moin/TimeComplexity.""" 4 | 5 | """ 6 | Summary 7 | # O(1) 8 | - .append('man') 9 | - .index('car') 10 | - .pop() 11 | - list[4] 12 | # O(n) 13 | - .pop('car') 14 | - .insert(3, 'car') 15 | - .reverse() 16 | - del operator (e.g. del list[2]) 17 | - contains (e.g. 'man' in list) 18 | - iteration (e.g. for e in list) 19 | NOTE: K means, depending on the other elements size. 20 | # O(k) 21 | - slice (e.g. list[:number]) 22 | - NOTE: getting the slice is O(k) deleting that slice is O(n) 23 | - concatenate (e.g. list + list_2) 24 | # O(n log n) 25 | - .sort() 26 | # O(nk) 27 | - list * list_2 28 | """ 29 | 30 | # .append('value'): Add element to the tail, O(1). 31 | el = ['car', 'hat', 'man'] 32 | el.append('key') # return ==> ['car', 'hat', 'man', 'key'] 33 | assert len(el) == 4 34 | 35 | # ====== 36 | # ====== 37 | 38 | # .pop(): Remove tail element, O(1). 39 | el = ['car', 'hat', 'man'] 40 | el.pop() # return ==> ['car', 'hat'] 41 | assert len(el) == 2 42 | 43 | # ====== 44 | # ====== 45 | 46 | # .index('value'): Returns the index of the first element(repeated or not), 47 | # O(1). 48 | el = ['car', 'ZZ', 'man', 'ZZ'] 49 | index = el.index('ZZ') # return ==> 0 50 | assert index == 1 51 | 52 | # ====== 53 | # ====== 54 | 55 | # .pop(index): Remove element by index, O(n). 56 | el = ['car', 'hat', 'man'] 57 | el.pop(0) # return ==> ['hat', 'man'] 58 | assert len(el) == 2 59 | 60 | # ====== 61 | # ====== 62 | 63 | # .remove('value'): Remove element by value, O(n). 64 | el = ['car', 'hat', 'man'] 65 | el.remove('car') # return ==> ['hat', 'man'] 66 | assert len(el) == 2 67 | 68 | # ====== 69 | # ====== 70 | 71 | # .insert('value'): Insert at index, O(n). 72 | el = ['car', 'hat', 'man'] 73 | el.insert(1, 'woman') # return ==> ['car', 'woman', 'hat', 'man'] 74 | assert len(el) == 4 75 | 76 | # ====== 77 | # ====== 78 | 79 | # .extend(list, set, tuple, iterator): Add collection of elements to the end of 80 | # the list, O(k), K means depending on the size of the iterator size. 81 | el = ['car', 'hat', 'man'] 82 | el_2 = ['c', 'h', 'm'] 83 | el.extend(el_2) # return ==> ['car', 'hat', 'man', 'c', 'h', 'm'] 84 | assert len(el) == 6 85 | 86 | # ====== 87 | # ====== 88 | 89 | 90 | # list.copy(): Returns a copy of the specified list, O(n). 91 | el = ['car', 'hat', 'man'] 92 | el_2 = el.copy() 93 | assert el == el_2 94 | # It is a shallow copy 95 | assert id(el) != id(el_2) 96 | 97 | # ====== 98 | # ====== 99 | 100 | # list.sort(): O(n log n). 101 | el = ['car', 'sat', 'hat', 'man'] 102 | el.sort() 103 | 104 | # ====== 105 | # ====== 106 | 107 | # list.count(): Returns the number of elements with the specified value, O(n). 108 | el = ['car', 'ZZ', 'man', 'ZZ'] 109 | count = el.count('ZZ') # return ==> 0 110 | assert count == 2 111 | 112 | # ====== 113 | # ====== 114 | 115 | # list.reverse(): O(n). 116 | el = ['car', 'man', 'ZZ'] 117 | el.reverse() 118 | assert el == ['ZZ', 'man', 'car'] 119 | 120 | # ====== 121 | # ====== 122 | 123 | len(el) # O(1) 124 | max(el) # O(n) 125 | min(el) # O(n) 126 | 'car' in el # O(n) 127 | 128 | # ====== 129 | # ====== 130 | 131 | el = ['car', 'man', 'ZZ'] 132 | el_2 = [1, 2, 3] 133 | # Here K means depending on the slice size. 134 | el[0: 1] # O(K) 135 | del el[0: 1] # O(n) 136 | # Here K means depending on the other list size want to concatenate. 137 | el + el_2 # O(K) 138 | -------------------------------------------------------------------------------- /dictionary_methods.py: -------------------------------------------------------------------------------- 1 | """ Efficiency cheat-sheet for Python dictionary data structure. 2 | More details at https://wiki.python.org/moin/TimeComplexity.""" 3 | 4 | """ 5 | Summary: 6 | * Since dictionary is built on the Hash Table concept that provides quick 7 | set(update value) and get(retrieve value) the efficiency of most operations will 8 | be in O(1). 9 | * But in rare cases(and depending on you usage) it may happen to be O(n) exception 10 | for some. 11 | * Iterating over a dictionary is O(n), as is copying the dictionary, since 12 | n(key/value) pairs must be copied. 13 | 14 | # O(1) 15 | - .get(key) 16 | - .update({key, value}) 17 | - .pop(key) returns value. 18 | - .popitem() ==> remove last key:value pair. 19 | - delete key:value pair (e.g. del dic[key]) 20 | - key in dic (e.g. 'a' in {'a': 1, 'b': 2}) 21 | # O(n) 22 | - .copy() 23 | - iteration (e.g. for i in dic) 24 | """ 25 | 26 | # .clear(): Removes all the elements from the dictionary 27 | dic = d = {'a': 10, 'b': 20, 'c': 30} 28 | dic.clear() 29 | assert len(dic) == 0 30 | 31 | # ======== 32 | # ======== 33 | 34 | 35 | # .get(key): Returns the value for a key if it exists in the dictionary, O(n). 36 | dic = d = {'a': 10, 'b': 20, 'c': 30} 37 | assert dic.get('b') == 20 38 | # Set default value to return, if get(key) not in the dictionary. 39 | dic.get('height', "Default msg: Value doesn't exist") 40 | 41 | # ======== 42 | # ======== 43 | 44 | # NOTE: More information about the view object in the important_notes.txt. 45 | 46 | # All the three methods return a view object of the dictionary. 47 | dic = d = {'a': 10, 'b': 20, 'c': 30} 48 | dic.items() # returns dict_items([(2, 'a'), (3, 'b')]) 49 | 50 | dic = {'a': 10, 'b': 20, 'c': 30} 51 | dic.keys() # returns dict_keys([2, 3]) 52 | 53 | dic = {'a': 10, 'b': 20, 'c': 30} 54 | dic.values() # returns dict_values(['a', 'b']) 55 | 56 | # ======== 57 | # ======== 58 | 59 | # .copy(): Returns a copy of the dictionary, O(n). 60 | dic = d = {'a': 10, 'b': 20, 'c': 30} 61 | assert dic.copy().get('b') == 20 62 | 63 | # ======== 64 | # ======== 65 | 66 | # .pop(key): Removes a key(and related value) from a dictionary, and returns its 67 | # value. 68 | dic = {'a': 10, 'b': 20, 'c': 30} 69 | assert dic.pop('a') == 10 70 | assert dic == {'b': 20, 'c': 30} 71 | # NOTE: .pop(key) raises a KeyError exception if key is not in dictionary. 72 | try: 73 | dic.pop('z') 74 | except KeyError: 75 | "Key doesn't exist" 76 | 77 | # ======== 78 | # ======== 79 | 80 | # .popitem(): Removes the last key-value pair added from d and returns it as a Python tuple. 81 | dic = {'a': 10, 'b': 20, 'c': 30} 82 | assert dic.popitem() == ('c', 30) 83 | assert dic == {'a': 10, 'b': 20} 84 | assert type(dic.popitem()) is tuple 85 | # NOTE: .popitem() raises a KeyError exception if dictionary is empty. 86 | try: 87 | dic.clear() 88 | dic.popitem() 89 | except KeyError: 90 | 'dictionary is empty' 91 | 92 | # ======== 93 | # ======== 94 | 95 | # .update(dict): Creates union between two dictionaries. 96 | dic_1 = {'a': 10, 'b': 20, 'c': 30} 97 | # NOTE: b and c are duplicated keys, thus its values will be updated. 98 | dic_2 = {'b': 'zx', 'd': 400, 'c': 'QW'} 99 | dic_1.update(dic_2) 100 | 101 | assert dic_1 == {'a': 10, 'b': 'zx', 'c': 'QW', 'd': 400} 102 | # WE can use a sequence of key-value pairs, similar to when the dict() function 103 | # is used to define a dictionary. 104 | dic_1 = {'a': 10, 'b': 20, 'c': 30} 105 | dic_1.update([('z', 'ZX'), ('c', 'QW')]) 106 | assert dic_1 == {'a': 10, 'b': 20, 'c': 'QW', 'z': 'ZX'} 107 | 108 | # Or the values to merge can be specified as a list of keyword arguments. 109 | d1 = {'a': 10, 'b': 20, 'c': 30} 110 | d1.update(b='updated', d=400) 111 | assert d1 == {'a': 10, 'b': 'updated', 'c': 30, 'd': 400} 112 | -------------------------------------------------------------------------------- /linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, value): 3 | self.value = value 4 | self.__next = None 5 | 6 | def __str__(self): 7 | string = f"Node({self.value})" 8 | return string 9 | 10 | def get_current_node_value(self): 11 | return self.value 12 | 13 | def set_next_node(self, value): 14 | self.__next = value 15 | 16 | def get_next_node(self): 17 | return self.__next 18 | 19 | 20 | class LinkedList: 21 | def __init__(self): 22 | self.__head = None 23 | self.__tail = None 24 | self.__size = 0 25 | 26 | def __str__(self): 27 | return f'LinkedList.head({self.__head})\nLinkedList.Tail({self.__tail})\nLinkedList.size({self.__size})' 28 | 29 | def delete_tail(self): 30 | """ Delete Tail and assign pre-last element as Tail. 31 | Access Tail Time complexity is O(n), while deleting Tail is O(1).""" 32 | 33 | if self.__size == 0: 34 | return "List is empty" 35 | else: 36 | # Three pointer respectively Head, Head.Next, Head.Next.Next. 37 | first_pointer = self.__head 38 | second_pointer = first_pointer.get_next_node() 39 | # If None pointers reached end of list. 40 | third_pointer = second_pointer.get_next_node() 41 | 42 | while third_pointer is not None: 43 | # Shift pointers by one step. 44 | first_pointer = second_pointer 45 | second_pointer = third_pointer 46 | third_pointer = second_pointer.get_next_node() 47 | else: 48 | # If third pointer is None, set first pointer as Tail. 49 | self.__tail = first_pointer 50 | self.__tail.set_next_node(None) 51 | self.__size -= 1 52 | 53 | def delete_head(self): 54 | """ Delete Head and assign Head.next as Head. 55 | Time complexity O(1).""" 56 | 57 | if self.__size == 0: 58 | return "List is empty" 59 | else: 60 | # Two pointer respectively Head, Head.Next. 61 | first_pointer = self.__head 62 | second_pointer = first_pointer.get_next_node() 63 | # Replace first pointer with second pointer. 64 | self.__head = second_pointer 65 | self.__size -= 1 66 | 67 | def delete_node(self, value): 68 | """ Delete node at specific position and shift list Nodes by one. 69 | Time complexity is O(n)).""" 70 | 71 | if self.__size == 0: 72 | return "List is empty" 73 | else: 74 | # Three pointer respectively Head, Head.Next, Head.Next.Next. 75 | first_pointer = self.__head 76 | second_pointer = first_pointer.get_next_node() 77 | third_pointer = second_pointer.get_next_node() 78 | 79 | while second_pointer.get_current_node_value() != value: 80 | # Shift pointers by one step. 81 | first_pointer = second_pointer 82 | second_pointer = third_pointer 83 | third_pointer = second_pointer.get_next_node() 84 | else: 85 | first_pointer.set_next_node(third_pointer) 86 | self.__size -= 1 87 | 88 | def get_tail(self): 89 | """ Time complexity O(1).""" 90 | 91 | if self.__tail is None: 92 | return None 93 | else: 94 | return self.__tail 95 | 96 | def get_head(self): 97 | """ Time complexity O(1).""" 98 | if self.__head is None: 99 | return None 100 | else: 101 | return self.__head 102 | 103 | def get_size(self): 104 | """ Time complexity O(1).""" 105 | return self.__size 106 | 107 | def get_node_position(self, value): 108 | """ Comparing argument value against each Node value and return its position. 109 | Time complexity O(n).""" 110 | 111 | # Set Head pointer. 112 | current_node = self.__head 113 | # Set position starter. 114 | position = 1 115 | 116 | # As pointer didn't reach end of the list. 117 | while current_node is not None: 118 | # If argument value equals Node value return Node position. 119 | if current_node.get_current_node_value() == value: 120 | return position 121 | # If argument value not equals Node value increment position by one. 122 | else: 123 | position += 1 124 | # Shift pointer to next Node. 125 | current_node = current_node.get_next_node() 126 | # If argument value doesn't evaluate with any Node value. 127 | return -1 128 | 129 | def append_node(self, value=None): 130 | """ Add Node to the end of the list. 131 | Time complexity O(1).""" 132 | 133 | if isinstance(value, Node): 134 | new_node = value 135 | else: 136 | new_node = Node(value) 137 | 138 | if self.__size == 0: 139 | self.__head = new_node 140 | else: 141 | self.__tail.set_next_node(new_node) 142 | 143 | self.__tail = new_node 144 | self.__size += 1 145 | 146 | def prepend_node(self, value=None): 147 | """ Add Node at the front of the list. 148 | Time complexity O(1).""" 149 | 150 | if self.__size == 0: 151 | self.append_node(value) 152 | else: 153 | current_head = self.__head 154 | 155 | if isinstance(value, Node): 156 | new_head = value 157 | else: 158 | new_head = Node(value) 159 | 160 | new_head.set_next_node(current_head) 161 | # Set new Node as Head. 162 | self.__head = new_head 163 | # Increment list size by one. 164 | self.__size += 1 165 | 166 | def add_node_after_node(self, value, position): 167 | """ Get Node by position and assign its next pointer to the new Node. 168 | Time complexity O(n).""" 169 | 170 | current_position = 0 171 | target_position = position - 1 172 | 173 | if not isinstance(value, Node): 174 | new_node = Node(value) 175 | else: 176 | new_node = value 177 | 178 | # Set two pointer respectively Head, Head.Next. 179 | first_pointer = self.__head 180 | second_pointer = first_pointer.get_next_node() 181 | 182 | if self.__size == 0: 183 | self.prepend_node(new_node) 184 | elif position == 0: 185 | first_pointer.set_next_node(new_node) 186 | return 187 | elif position > self.__size: 188 | self.append_node(new_node) 189 | return 190 | 191 | while current_position != target_position: 192 | # Shift pointers by one step. 193 | first_pointer = second_pointer 194 | second_pointer = first_pointer.get_next_node() 195 | current_position += 1 196 | else: 197 | first_pointer.set_next_node(new_node) 198 | new_node.set_next_node(second_pointer) 199 | 200 | def get_all_nodes(self): 201 | """ Loop over all Nodes and print them out. 202 | Time complexity O(n).""" 203 | if self.__size == 0: 204 | return "List is empty" 205 | 206 | first_pointer = self.__head 207 | 208 | while first_pointer is not None: 209 | print(first_pointer) 210 | first_pointer = first_pointer.get_next_node() 211 | --------------------------------------------------------------------------------