From d13b78a05760b2653d75ca5363d7d485142468ba Mon Sep 17 00:00:00 2001 From: BrianLusina <12752833+BrianLusina@users.noreply.github.com> Date: Fri, 10 May 2024 13:26:34 +0300 Subject: [PATCH 1/7] feat(circular-linked-list): init circular linked list --- datastructures/linked_lists/__init__.py | 25 ++-- .../linked_lists/circular/__init__.py | 133 ++++++++++++++++++ datastructures/linked_lists/circular/node.py | 11 ++ .../circular/test_circular_linked_list.py | 25 ++++ 4 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 datastructures/linked_lists/circular/__init__.py create mode 100644 datastructures/linked_lists/circular/node.py create mode 100644 datastructures/linked_lists/circular/test_circular_linked_list.py diff --git a/datastructures/linked_lists/__init__.py b/datastructures/linked_lists/__init__.py index 4ae0a3b2..aeeb8f84 100755 --- a/datastructures/linked_lists/__init__.py +++ b/datastructures/linked_lists/__init__.py @@ -1,39 +1,40 @@ -# coding=utf-8 +from typing import Any, Union, Optional, Generic, TypeVar from abc import ABCMeta, abstractmethod -from typing import Any, Union, Optional from datastructures.linked_lists.exceptions import EmptyLinkedList +T = TypeVar("T") -class Node: + +class Node(Generic[T]): """ Node object in the Linked List """ __metaclass__ = ABCMeta - def __init__(self, data=Optional[Any], next_=None, key=None): + def __init__(self, data: Optional[T] = None, next_: Optional['Node[Generic[T]]'] = None, key: Any = None): self.data = data - self.next: Optional[Node] = next_ + self.next = next_ self.key = key - def __str__(self): + def __str__(self) -> str: return f"Node({self.data})" - def __repr__(self): + def __repr__(self) -> str: return f"Node({self.data})" - def __eq__(self, other: "Node"): + def __eq__(self, other: "Node") -> bool: return self.data == other.data -class LinkedList: +class LinkedList(Generic[T]): """ The most basic LinkedList from which other types of Linked List will be subclassed """ __metaclass__ = ABCMeta - head: Optional[Node] = None + head: Optional[Node[Generic[T]]] = None def __init__(self): self.head = None @@ -52,11 +53,11 @@ def __iter__(self): @abstractmethod def __str__(self): - raise NotImplementedError("Not Yet implemented") + return "->".join([str(item) for item in self]) @abstractmethod def __repr__(self): - raise NotImplementedError("Not Yet implemented") + return "->".join([str(item) for item in self]) def __len__(self): """ diff --git a/datastructures/linked_lists/circular/__init__.py b/datastructures/linked_lists/circular/__init__.py new file mode 100644 index 00000000..c4133aec --- /dev/null +++ b/datastructures/linked_lists/circular/__init__.py @@ -0,0 +1,133 @@ +from typing import Optional, Any, Union + +from datastructures.linked_lists import LinkedList, Node, T +from .node import CircularNode + + +class CircularLinkedList(LinkedList): + head: Optional[CircularNode] = None + + def __init__(self): + super().__init__() + + def __str__(self): + return super().__str__() + + def __repr__(self): + return super().__repr__() + + def __iter__(self): + current = self.head + while current: + yield current.data + # break out of the loop when the next node is back at the head node to avoid a continuous loop + if current.next == self.head: + break + current = current.next + + def append(self, data: T): + """ + Adds a new node to the end of this linked list. To maintain circular pointers where the last node points back + to the head node, the pointer keeping track of the nodes as it traverses through the list checks to see if the + next pointer equals the head node. If it is, the pointer exists the loop, otherwise this becomes an infinite + loop. Once the pointer is at the end, the last node's next pointer is set to the new node and the new node's + next pointer is set to the head node. + If there is not head node, then the new node becomes the head node and it's next pointer points to itself. + Args: + data T: data to insert in the linked list + """ + new_node = CircularNode(value=data) + if not self.head: + self.head = new_node + self.head.next = self.head + else: + new_node = CircularNode(value=data) + current = self.head + while current.next != self.head: + current = current.next + current.next = new_node + new_node.next = self.head + + def prepend(self, data): + pass + + def reverse(self): + pass + + def insert(self, node, pos): + pass + + def insert_after_node(self, prev: Any, data: Any): + pass + + def unshift(self, node): + pass + + def shift(self): + pass + + def pop(self) -> Optional[Node]: + pass + + def delete_node(self, node: Node): + pass + + def delete_node_at_position(self, position: int): + pass + + def delete_node_by_data(self, data: Any): + pass + + def delete_nodes_by_data(self, data: Any): + pass + + def delete_middle_node(self) -> Optional[Node]: + pass + + def display(self): + pass + + def display_forward(self): + pass + + def display_backward(self): + pass + + def alternate_split(self): + pass + + def is_palindrome(self) -> bool: + pass + + def pairwise_swap(self) -> Node: + pass + + def swap_nodes_at_kth_and_k_plus_1(self, k: int) -> Node: + pass + + def move_to_front(self, node: Node): + pass + + def move_tail_to_head(self): + pass + + def partition(self, data: Any) -> Union[Node, None]: + pass + + def remove_tail(self): + pass + + def remove_duplicates(self) -> Optional[Node]: + pass + + def rotate(self, k: int): + pass + + def reverse_groups(self, k: int): + pass + + def odd_even_list(self) -> Optional[Node]: + pass + + def maximum_pair_sum(self) -> int: + pass diff --git a/datastructures/linked_lists/circular/node.py b/datastructures/linked_lists/circular/node.py new file mode 100644 index 00000000..d594671d --- /dev/null +++ b/datastructures/linked_lists/circular/node.py @@ -0,0 +1,11 @@ +from typing import Optional, Self +from datastructures.linked_lists import Node, T + + +class CircularNode(Node): + """ + CircularNode implementation in a circular linked list + """ + + def __init__(self, value: T, next_: Optional[Self] = None): + super().__init__(value, next_) diff --git a/datastructures/linked_lists/circular/test_circular_linked_list.py b/datastructures/linked_lists/circular/test_circular_linked_list.py new file mode 100644 index 00000000..4840bace --- /dev/null +++ b/datastructures/linked_lists/circular/test_circular_linked_list.py @@ -0,0 +1,25 @@ +import unittest +from . import CircularLinkedList + + +class CircularLinkedListAppendTestCase(unittest.TestCase): + def test_1(self): + """should append a new node 7 to linked list [1,2,3,4,5,6] to become [1,2,3,4,5,6,7]""" + data = [1, 2, 3, 4, 5, 6] + expected = [1, 2, 3, 4, 5, 6, 7] + linked_list = CircularLinkedList() + + for d in data: + linked_list.append(d) + + linked_list.append(7) + + actual_head = linked_list.head + self.assertIsNotNone(actual_head) + + self.assertEqual(1, actual_head.data) + self.assertEqual(expected, list(linked_list)) + + +if __name__ == '__main__': + unittest.main() From cc79e80d009492433f892a1e30163d39f698dbd0 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Fri, 10 May 2024 10:27:03 +0000 Subject: [PATCH 2/7] updating DIRECTORY.md --- DIRECTORY.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 02970391..228a3e38 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -163,6 +163,9 @@ * Hashset * [Test My Hashset](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/hashset/test_my_hashset.py) * Linked Lists + * Circular + * [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/node.py) + * [Test Circular Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list.py) * Doubly Linked List * [Test Doubly Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/doubly_linked_list/test_doubly_linked_list.py) * [Test Doubly Linked List Move Tail To Head](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/doubly_linked_list/test_doubly_linked_list_move_tail_to_head.py) From 4548a98dfb3c2e784a010f84f923f3519eac5328 Mon Sep 17 00:00:00 2001 From: BrianLusina <12752833+BrianLusina@users.noreply.github.com> Date: Thu, 16 May 2024 08:07:22 +0300 Subject: [PATCH 3/7] feat(circular-linked-list): prepend data --- .../linked_lists/circular/__init__.py | 15 ++++++++++++--- .../circular/test_circular_linked_list.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/datastructures/linked_lists/circular/__init__.py b/datastructures/linked_lists/circular/__init__.py index c4133aec..aef46fa5 100644 --- a/datastructures/linked_lists/circular/__init__.py +++ b/datastructures/linked_lists/circular/__init__.py @@ -41,15 +41,24 @@ def append(self, data: T): self.head = new_node self.head.next = self.head else: - new_node = CircularNode(value=data) current = self.head while current.next != self.head: current = current.next current.next = new_node new_node.next = self.head - def prepend(self, data): - pass + def prepend(self, data: T): + new_node = CircularNode(value=data) + current = self.head + new_node.next = self.head + + if not self.head: + new_node.next = new_node + else: + while current.next != self.head: + current = current.next + current.next = new_node + self.head = new_node def reverse(self): pass diff --git a/datastructures/linked_lists/circular/test_circular_linked_list.py b/datastructures/linked_lists/circular/test_circular_linked_list.py index 4840bace..427fbbb1 100644 --- a/datastructures/linked_lists/circular/test_circular_linked_list.py +++ b/datastructures/linked_lists/circular/test_circular_linked_list.py @@ -21,5 +21,24 @@ def test_1(self): self.assertEqual(expected, list(linked_list)) +class CircularLinkedListPrependTestCase(unittest.TestCase): + def test_1(self): + """should prepend a new node 7 to linked list [1,2,3,4,5,6] to become [7,1,2,3,4,5,6]""" + data = [1, 2, 3, 4, 5, 6] + expected = [7, 1, 2, 3, 4, 5, 6] + linked_list = CircularLinkedList() + + for d in data: + linked_list.append(d) + + linked_list.prepend(7) + + actual_head = linked_list.head + self.assertIsNotNone(actual_head) + + self.assertEqual(7, actual_head.data) + self.assertEqual(expected, list(linked_list)) + + if __name__ == '__main__': unittest.main() From 733a2a79617ec8468292f1c9d5dbfe346e8124d5 Mon Sep 17 00:00:00 2001 From: BrianLusina <12752833+BrianLusina@users.noreply.github.com> Date: Thu, 16 May 2024 09:00:16 +0300 Subject: [PATCH 4/7] feat(circular-linked-list): remove node by key --- datastructures/linked_lists/__init__.py | 39 ++++++------- .../linked_lists/circular/__init__.py | 57 ++++++++++++++++--- datastructures/linked_lists/circular/node.py | 6 +- .../circular/test_circular_linked_list.py | 15 +++++ .../doubly_linked_list/__init__.py | 8 +-- .../singly_linked_list/__init__.py | 10 ++-- 6 files changed, 95 insertions(+), 40 deletions(-) diff --git a/datastructures/linked_lists/__init__.py b/datastructures/linked_lists/__init__.py index aeeb8f84..ecbcc702 100755 --- a/datastructures/linked_lists/__init__.py +++ b/datastructures/linked_lists/__init__.py @@ -16,16 +16,17 @@ class Node(Generic[T]): def __init__(self, data: Optional[T] = None, next_: Optional['Node[Generic[T]]'] = None, key: Any = None): self.data = data self.next = next_ - self.key = key + # if no key is provided, the hash of the data becomes the key + self.key = key or hash(data) def __str__(self) -> str: - return f"Node({self.data})" + return f"Node(data={self.data}, key={self.key})" def __repr__(self) -> str: - return f"Node({self.data})" + return f"Node(data={self.data}, key={self.key})" def __eq__(self, other: "Node") -> bool: - return self.data == other.data + return self.key == other.key class LinkedList(Generic[T]): @@ -34,19 +35,18 @@ class LinkedList(Generic[T]): """ __metaclass__ = ABCMeta - head: Optional[Node[Generic[T]]] = None - def __init__(self): - self.head = None + def __init__(self, head: Optional[Node[Generic[T]]] = None): + self.head: Optional[Node[Generic[T]]] = head def __iter__(self): - head = self.head + current = self.head - if head: - yield head.data + if current: + yield current.data - if head.next: - node = head.next + if current.next: + node = current.next while node: yield node.data node = node.next @@ -282,23 +282,24 @@ def delete_node_at_position(self, position: int): # rest of the implementation is at the relevant subclasses @abstractmethod - def delete_node_by_data(self, data: Any): + def delete_node_by_key(self, key: Any): """ traverses the LinkedList until we find the data in a Node that matches and deletes that node. This uses the same approach as self.delete_node(node: Node) but instead of using the node to traverse the linked list, we use the data attribute of a node. Note that if there are duplicate Nodes in the LinkedList with the same data attributes only, the first Node is deleted. - :param data: Data of Node element to be deleted + Args: + key Any: Key of Node element to be deleted """ raise NotImplementedError("Not yet implemented") @abstractmethod - def delete_nodes_by_data(self, data: Any): + def delete_nodes_by_key(self, key: Any): """ - traverses the LinkedList until we find the data in a Node that matches and deletes those nodes. This uses the - same approach as self.delete_node(node: Node) but instead of using the node to traverse the linked list, - we use the data attribute of a node. - :param data: Data of Node element to be deleted + traverses the LinkedList until we find the key in a Node that matches and deletes those nodes. This uses the + same approach as self.delete_node_by_key(key) but instead deletes multiple nodes with the same key + Args: + key Any: Key of Node elements to be deleted """ raise NotImplementedError("Not yet implemented") diff --git a/datastructures/linked_lists/circular/__init__.py b/datastructures/linked_lists/circular/__init__.py index aef46fa5..67166ecb 100644 --- a/datastructures/linked_lists/circular/__init__.py +++ b/datastructures/linked_lists/circular/__init__.py @@ -5,10 +5,8 @@ class CircularLinkedList(LinkedList): - head: Optional[CircularNode] = None - - def __init__(self): - super().__init__() + def __init__(self, head: Optional[CircularNode] = None): + super().__init__(head) def __str__(self): return super().__str__() @@ -78,16 +76,57 @@ def shift(self): def pop(self) -> Optional[Node]: pass - def delete_node(self, node: Node): + def delete_node(self, n: Node): pass def delete_node_at_position(self, position: int): pass - def delete_node_by_data(self, data: Any): - pass - - def delete_nodes_by_data(self, data: Any): + def delete_node_by_key(self, key: Any): + if self.head: + # if the head node's key matches the key we are looking for + if self.head.key == key: + # set the current pointer to the head node. This will be used to track the last node as the pointer + # moves through the list + current = self.head + # move through the list until we reach the pointer that points batck to the head node. + while current.next != self.head: + current = current.next + + # if the head node equals the next node, that means that this linked list has a length of 1, i.e. just 1 + # node. The head node can be set to None + if self.head == self.head.next: + self.head = None + else: + # set the current pointer to point to the current head's next + current.next = self.head.next + # set the head to now become the next node + self.head = self.head.next + else: + # we have a situation where the head node's key is not equal to the head node, therefore, we need to + # traverse the list to find the first node whose key matches the given key. Setting current to the head + # node acts as the pointer that we keep track of + current = self.head + # previous pointer helps to keep track of the previous node as we traverse, it is initially set to None + previous: Optional[CircularNode] = None + + # we iterate through the linked list as long as the next pointer of the current head is not equal to + # the head node. This is to avoid an infinite loop as this is a circular linked list. + while current.next != self.head: + # we set the previous pointer to the current node to keep track of the node before we reset the + # current pointer to the next node + previous = current + # move the current pointer to the next node + current = current.next + # if the current node's key is equal to the key we are searching for + if current.key == key: + # we set the previous node's next pointer to point to the current node's next pointer. + # Essentially removing the current node from the list + previous.next = current.next + # set the current node to the current's next node + current = current.next + + def delete_nodes_by_key(self, key: T): pass def delete_middle_node(self) -> Optional[Node]: diff --git a/datastructures/linked_lists/circular/node.py b/datastructures/linked_lists/circular/node.py index d594671d..d8f39ead 100644 --- a/datastructures/linked_lists/circular/node.py +++ b/datastructures/linked_lists/circular/node.py @@ -1,4 +1,4 @@ -from typing import Optional, Self +from typing import Optional, Self, Any from datastructures.linked_lists import Node, T @@ -7,5 +7,5 @@ class CircularNode(Node): CircularNode implementation in a circular linked list """ - def __init__(self, value: T, next_: Optional[Self] = None): - super().__init__(value, next_) + def __init__(self, value: T, next_: Optional[Self] = None, key: Optional[Any] = None): + super().__init__(value, next_, key) diff --git a/datastructures/linked_lists/circular/test_circular_linked_list.py b/datastructures/linked_lists/circular/test_circular_linked_list.py index 427fbbb1..3a38cf01 100644 --- a/datastructures/linked_lists/circular/test_circular_linked_list.py +++ b/datastructures/linked_lists/circular/test_circular_linked_list.py @@ -40,5 +40,20 @@ def test_1(self): self.assertEqual(expected, list(linked_list)) +class CircularLinkedListDeleteNodeByKeyTestCase(unittest.TestCase): + def test_1(self): + """should delete a node 5 from linked list [1,2,3,4,5,6] to become [1,2,3,4,6]""" + data = [1, 2, 3, 4, 5, 6] + expected = [1, 2, 3, 4, 6] + circular_linked_list = CircularLinkedList() + + for d in data: + circular_linked_list.append(d) + + circular_linked_list.delete_node_by_key(5) + + self.assertEqual(expected, list(circular_linked_list)) + + if __name__ == '__main__': unittest.main() diff --git a/datastructures/linked_lists/doubly_linked_list/__init__.py b/datastructures/linked_lists/doubly_linked_list/__init__.py index 5f14f5e1..9b78c5f7 100755 --- a/datastructures/linked_lists/doubly_linked_list/__init__.py +++ b/datastructures/linked_lists/doubly_linked_list/__init__.py @@ -48,7 +48,7 @@ def __len__(self): return count - def delete_nodes_by_data(self, data: Any): + def delete_nodes_by_key(self, key: Any): pass def append(self, data: Any): @@ -264,12 +264,12 @@ def delete_node(self, node: DoubleNode): current_node = current_node.next - def delete_node_by_data(self, data: Any): + def delete_node_by_key(self, key: Any): current = self.head # in the event we have a head node and the head node's data matches the data we intend to remove from the Linked # List, then we simply re-assign the head node to the next node - if current and current.data == data: + if current and current.data == key: self.head = current.next current = None return @@ -278,7 +278,7 @@ def delete_node_by_data(self, data: Any): previous = None # we move the pointer down the LinkedList until we find the Node whose data matches what we want to delete - while current and current.data != data: + while current and current.data != key: previous = current current = current.next diff --git a/datastructures/linked_lists/singly_linked_list/__init__.py b/datastructures/linked_lists/singly_linked_list/__init__.py index 125b6804..536c5de2 100755 --- a/datastructures/linked_lists/singly_linked_list/__init__.py +++ b/datastructures/linked_lists/singly_linked_list/__init__.py @@ -151,12 +151,12 @@ def delete_node(self, single_node: SingleNode): previous_node = current_node current_node = current_node.next - def delete_node_by_data(self, data: Any): + def delete_node_by_key(self, key: Any): current = self.head # in the event we have a head node and the head node's data matches the data we intend to remove from the Linked # List, then we simply re-assign the head node to the next node - if current and current.data == data: + if current and current.data == key: self.head = current.next return @@ -164,7 +164,7 @@ def delete_node_by_data(self, data: Any): previous = None # we move the pointer down the LinkedList until we find the Node whose data matches what we want to delete - while current and current.data != data: + while current and current.data != key: previous = current current = current.next @@ -177,13 +177,13 @@ def delete_node_by_data(self, data: Any): previous.next = current.next return - def delete_nodes_by_data(self, data: Any): + def delete_nodes_by_key(self, key: Any): dummy_head = SingleNode(-1) dummy_head.next = self.head current = dummy_head while current.next: - if current.next.data == data: + if current.next.data == key: current.next = current.next.next else: current = current.next From a01fa48d568e6f47b69c9663210d7026497513a0 Mon Sep 17 00:00:00 2001 From: BrianLusina <12752833+BrianLusina@users.noreply.github.com> Date: Mon, 20 May 2024 11:04:08 +0300 Subject: [PATCH 5/7] feat(circular-linked-list): split list --- datastructures/linked_lists/__init__.py | 6 +-- .../linked_lists/circular/__init__.py | 49 ++++++++++++++++++- .../circular/test_circular_linked_list.py | 16 ++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/datastructures/linked_lists/__init__.py b/datastructures/linked_lists/__init__.py index ecbcc702..5d7ce407 100755 --- a/datastructures/linked_lists/__init__.py +++ b/datastructures/linked_lists/__init__.py @@ -59,12 +59,12 @@ def __str__(self): def __repr__(self): return "->".join([str(item) for item in self]) - def __len__(self): + def __len__(self) -> int: """ Implements the len() for a linked list. This counts the number of nodes in a Linked List This uses an iterative method to find the length of the LinkedList - :return: Number of nodes - :rtype: int + Returns: + int: Number of nodes """ return len(tuple(iter(self))) diff --git a/datastructures/linked_lists/circular/__init__.py b/datastructures/linked_lists/circular/__init__.py index 67166ecb..ab53faa3 100644 --- a/datastructures/linked_lists/circular/__init__.py +++ b/datastructures/linked_lists/circular/__init__.py @@ -1,4 +1,4 @@ -from typing import Optional, Any, Union +from typing import Optional, Any, Union, Tuple, Self from datastructures.linked_lists import LinkedList, Node, T from .node import CircularNode @@ -23,6 +23,16 @@ def __iter__(self): break current = current.next + def __len__(self) -> int: + current = self.head + count = 0 + while current: + count += 1 + current = current.next + if current == self.head: + break + return count + def append(self, data: T): """ Adds a new node to the end of this linked list. To maintain circular pointers where the last node points back @@ -144,6 +154,43 @@ def display_backward(self): def alternate_split(self): pass + def split_list(self) -> Optional[Tuple[Self, Optional[Self]]]: + """ + Splits a circular linked list into two halves and returns the two halves in a tuple. If the size is 0, i.e. no + nodes are in this linked list, then it returns None. If the size is 1, then the first portion of the tuple, at + index 0 will be the head of this circular linked list, while the second portion will be None. + Returns: + Tuple: tuple with two circular linked lists + """ + size = len(self) + + if size == 0: + return None + if size == 1: + return self.head, None + + mid = size // 2 + count = 0 + + previous: Optional[CircularNode] = None + current = self.head + + while current and count < mid: + count += 1 + previous = current + current = current.next + + previous.next = self.head + + second_list = CircularLinkedList() + while current.next != self.head: + second_list.append(current.data) + current = current.next + + second_list.append(current.data) + + return self, second_list + def is_palindrome(self) -> bool: pass diff --git a/datastructures/linked_lists/circular/test_circular_linked_list.py b/datastructures/linked_lists/circular/test_circular_linked_list.py index 3a38cf01..a4debea4 100644 --- a/datastructures/linked_lists/circular/test_circular_linked_list.py +++ b/datastructures/linked_lists/circular/test_circular_linked_list.py @@ -55,5 +55,21 @@ def test_1(self): self.assertEqual(expected, list(circular_linked_list)) +class CircularLinkedListSplitListTestCase(unittest.TestCase): + def test_1(self): + """should split a linked list [1,2,3,4,5,6] to become ([1,2,3],[4,5,6])""" + data = [1, 2, 3, 4, 5, 6] + expected = ([1, 2, 3], [4, 5, 6]) + circular_linked_list = CircularLinkedList() + + for d in data: + circular_linked_list.append(d) + + first_list, second_list = circular_linked_list.split_list() + + self.assertEqual(expected[0], list(first_list)) + self.assertEqual(expected[1], list(second_list)) + + if __name__ == '__main__': unittest.main() From 0ee4e0647f43a905e4ef4234e4e21860cd088458 Mon Sep 17 00:00:00 2001 From: BrianLusina <12752833+BrianLusina@users.noreply.github.com> Date: Wed, 22 May 2024 09:44:43 +0300 Subject: [PATCH 6/7] feat(circular-linked-list): josephus circle --- algorithms/josephus_circle/README.md | 19 ++++++++ algorithms/josephus_circle/__init__.py | 18 ++++++++ .../josephus_circle/test_josephus_circle.py | 22 +++++++++ .../linked_lists/circular/__init__.py | 45 ++++++++++++++++++- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 algorithms/josephus_circle/README.md create mode 100644 algorithms/josephus_circle/__init__.py create mode 100644 algorithms/josephus_circle/test_josephus_circle.py diff --git a/algorithms/josephus_circle/README.md b/algorithms/josephus_circle/README.md new file mode 100644 index 00000000..4b11f520 --- /dev/null +++ b/algorithms/josephus_circle/README.md @@ -0,0 +1,19 @@ +# Josephus Circle + +Children often play a counting-out game to randomly select one person from the group by singing a rhyme. The purpose is +to select one person, either as a straightforward winner, or as someone who is eliminated. + +Josephus problem is related to this concept. In this problem, people are standing in one circle waiting to be executed. +Following points list the specifications of Josephus problem: + +The counting out begins at a specified point in a circle and continues around the circle in a fixed direction. + +In each step, a certain number of people are skipped and the next person is executed. + +For example, if we have 𝑛 people, and π‘˜-1 people are skipped every time, it means that the +π‘˜th person is executed. Here, π‘˜ is the step-size. + +## Reference + +- https://en.wikipedia.org/wiki/Josephus_problem +- \ No newline at end of file diff --git a/algorithms/josephus_circle/__init__.py b/algorithms/josephus_circle/__init__.py new file mode 100644 index 00000000..ad9baf4d --- /dev/null +++ b/algorithms/josephus_circle/__init__.py @@ -0,0 +1,18 @@ +from datastructures.linked_lists.circular import CircularLinkedList +from datastructures.linked_lists.circular.node import CircularNode + + +def josephus_circle(circular_list: CircularLinkedList, step: int) -> CircularNode: + current = circular_list.head + + length = len(circular_list) + while length > 1: + count = 1 + while count != step: + current = current.next + count += 1 + circular_list.delete_node(current) + current = current.next + length -= 1 + + return current diff --git a/algorithms/josephus_circle/test_josephus_circle.py b/algorithms/josephus_circle/test_josephus_circle.py new file mode 100644 index 00000000..59b62102 --- /dev/null +++ b/algorithms/josephus_circle/test_josephus_circle.py @@ -0,0 +1,22 @@ +import unittest +from datastructures.linked_lists.circular import CircularLinkedList +from datastructures.linked_lists.circular.node import CircularNode +from . import josephus_circle + + +class JosephusCircularTestCase(unittest.TestCase): + def test_1(self): + """should run through the circle of 1-2-3-4 with a step of 2 remaining with 1""" + data = [1, 2, 3, 4] + circular_linked_list = CircularLinkedList() + for d in data: + circular_linked_list.append(d) + + step = 2 + expected = CircularNode(1) + actual = josephus_circle(circular_linked_list, step) + self.assertEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main() diff --git a/datastructures/linked_lists/circular/__init__.py b/datastructures/linked_lists/circular/__init__.py index ab53faa3..9d636cfa 100644 --- a/datastructures/linked_lists/circular/__init__.py +++ b/datastructures/linked_lists/circular/__init__.py @@ -86,8 +86,49 @@ def shift(self): def pop(self) -> Optional[Node]: pass - def delete_node(self, n: Node): - pass + def delete_node(self, node_: CircularNode): + if self.head: + # if the head node matches the node we are looking for + if self.head == node_: + # set the current pointer to the head node. This will be used to track the last node as the pointer + # moves through the list + current = self.head + # move through the list until we reach the pointer that points batck to the head node. + while current.next != self.head: + current = current.next + + # if the head node equals the next node, that means that this linked list has a length of 1, i.e. just 1 + # node. The head node can be set to None + if self.head == self.head.next: + self.head = None + else: + # set the current pointer to point to the current head's next + current.next = self.head.next + # set the head to now become the next node + self.head = self.head.next + else: + # we have a situation where the head node's key is not equal to the head node, therefore, we need to + # traverse the list to find the first node whose key matches the given key. Setting current to the head + # node acts as the pointer that we keep track of + current = self.head + # previous pointer helps to keep track of the previous node as we traverse, it is initially set to None + previous: Optional[CircularNode] = None + + # we iterate through the linked list as long as the next pointer of the current head is not equal to + # the head node. This is to avoid an infinite loop as this is a circular linked list. + while current.next != self.head: + # we set the previous pointer to the current node to keep track of the node before we reset the + # current pointer to the next node + previous = current + # move the current pointer to the next node + current = current.next + # if the current node's key is equal to the key we are searching for + if current == node_: + # we set the previous node's next pointer to point to the current node's next pointer. + # Essentially removing the current node from the list + previous.next = current.next + # set the current node to the current's next node + current = current.next def delete_node_at_position(self, position: int): pass From 7b1c5e262c90e1aef4c950519ab1c257be00df4d Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Wed, 22 May 2024 06:45:13 +0000 Subject: [PATCH 7/7] updating DIRECTORY.md --- DIRECTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 228a3e38..d874967c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -62,6 +62,8 @@ * Huffman * [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py) * [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py) + * Josephus Circle + * [Test Josephus Circle](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/josephus_circle/test_josephus_circle.py) * Memoization * [Fibonacci](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/memoization/fibonacci.py) * [Petethebaker](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/petethebaker.py)