Skip to content

Commit

Permalink
Merge branch 'master' into Allgood
Browse files Browse the repository at this point in the history
  • Loading branch information
Arvind-raj06 committed Feb 26, 2021
2 parents b315c5e + bcf9753 commit e3586fa
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 25 deletions.
3 changes: 2 additions & 1 deletion pydatastructs/linear_data_structures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
SinglyLinkedList,
DoublyLinkedList,
SinglyCircularLinkedList,
DoublyCircularLinkedList
DoublyCircularLinkedList,
SkipList
)
__all__.extend(linked_lists.__all__)

Expand Down
209 changes: 199 additions & 10 deletions pydatastructs/linear_data_structures/linked_lists.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from pydatastructs.utils.misc_util import _check_type, LinkedListNode
from pydatastructs.utils.misc_util import _check_type, LinkedListNode, SkipNode
import math, random

__all__ = [
'SinglyLinkedList',
'DoublyLinkedList',
'SinglyCircularLinkedList',
'DoublyCircularLinkedList'
'DoublyCircularLinkedList',
'SkipList'
]

class LinkedList(object):
Expand Down Expand Up @@ -219,12 +221,12 @@ class DoublyLinkedList(LinkedList):
>>> dll.append(5)
>>> dll.appendleft(2)
>>> str(dll)
"['2', '6', '5']"
"['(2, None)', '(6, None)', '(5, None)']"
>>> dll[0].key = 7.2
>>> dll.extract(1).key
6
>>> str(dll)
"['7.2', '5']"
"['(7.2, None)', '(5, None)']"
References
==========
Expand Down Expand Up @@ -290,6 +292,11 @@ def insert_at(self, index, key, data=None):
if self.size == 1:
self.head, self.tail = \
new_node, new_node
elif index == self.size - 1:
new_node.prev = self.tail
new_node.next = self.tail.next
self.tail.next = new_node
self.tail = new_node
else:
counter = 0
current_node = self.head
Expand Down Expand Up @@ -354,12 +361,12 @@ class SinglyLinkedList(LinkedList):
>>> sll.append(5)
>>> sll.appendleft(2)
>>> str(sll)
"['2', '6', '5']"
"['(2, None)', '(6, None)', '(5, None)']"
>>> sll[0].key = 7.2
>>> sll.extract(1).key
6
>>> str(sll)
"['7.2', '5']"
"['(7.2, None)', '(5, None)']"
References
==========
Expand Down Expand Up @@ -409,6 +416,10 @@ def insert_at(self, index, key, data=None):
if self.size == 1:
self.head, self.tail = \
new_node, new_node
elif index == self.size - 1:
new_node.next = self.tail.next
self.tail.next = new_node
self.tail = new_node
else:
counter = 0
current_node = self.head
Expand Down Expand Up @@ -469,12 +480,12 @@ class SinglyCircularLinkedList(SinglyLinkedList):
>>> scll.append(5)
>>> scll.appendleft(2)
>>> str(scll)
"['2', '6', '5']"
"['(2, None)', '(6, None)', '(5, None)']"
>>> scll[0].key = 7.2
>>> scll.extract(1).key
6
>>> str(scll)
"['7.2', '5']"
"['(7.2, None)', '(5, None)']"
References
==========
Expand Down Expand Up @@ -528,12 +539,12 @@ class DoublyCircularLinkedList(DoublyLinkedList):
>>> dcll.append(5)
>>> dcll.appendleft(2)
>>> str(dcll)
"['2', '6', '5']"
"['(2, None)', '(6, None)', '(5, None)']"
>>> dcll[0].key = 7.2
>>> dcll.extract(1).key
6
>>> str(dcll)
"['7.2', '5']"
"['(7.2, None)', '(5, None)']"
References
==========
Expand Down Expand Up @@ -581,3 +592,181 @@ def extract(self, index):
elif index == 0:
self.tail.next = self.head
return node

class SkipList(object):
"""
Represents Skip List
Examples
========
>>> from pydatastructs import SkipList
>>> sl = SkipList()
>>> sl.insert(6)
>>> sl.insert(1)
>>> sl.insert(3)
>>> node = sl.extract(1)
>>> str(node)
'(1, None)'
>>> sl.insert(4)
>>> sl.insert(2)
>>> sl.search(4)
True
>>> sl.search(10)
False
"""

__slots__ = ['head', 'tail', '_levels', '_num_nodes', 'seed']

def __new__(cls):
obj = object.__new__(cls)
obj.head, obj.tail = None, None
obj._num_nodes = 0
obj._levels = 0
obj._add_level()
return obj

@classmethod
def methods(cls):
return ['__new__', 'levels', 'search',
'extract', '__str__', 'size']

def _add_level(self):
self.tail = SkipNode(math.inf, next=None, down=self.tail)
self.head = SkipNode(-math.inf, next=self.tail, down=self.head)
self._levels += 1

@property
def levels(self):
"""
Returns the number of levels in the
current skip list.
"""
return self._levels

def _search(self, key) -> list:
path = []
node = self.head
while node:
if node.next.key >= key:
path.append(node)
node = node.down
else:
node = node.next
return path

def search(self, key) -> bool:
return self._search(key)[-1].next.key == key

def insert(self, key, data=None):
"""
Inserts a new node to the skip list.
Parameters
==========
key
Any valid identifier to uniquely
identify the node in the linked list.
data
Any valid data to be stored in the node.
"""
path = self._search(key)
tip = path[-1]
below = SkipNode(key=key, data=data, next=tip.next)
tip.next = below
total_level = self._levels
level = 1
while random.getrandbits(1) % 2 == 0 and level <= total_level:
if level == total_level:
self._add_level()
prev = self.head
else:
prev = path[total_level - 1 - level]
below = SkipNode(key=key, data=None, next=prev.next, down=below)
prev.next = below
level += 1
self._num_nodes += 1

@property
def size(self):
return self._num_nodes

def extract(self, key):
"""
Extracts the node with the given key in the skip list.
Parameters
==========
key
The key of the node under consideration.
Returns
=======
return_node: SkipNode
The node with given key.
"""
path = self._search(key)
tip = path[-1]
if tip.next.key != key:
raise KeyError('Node with key %s is not there in %s'%(key, self))
return_node = SkipNode(tip.next.key, tip.next.data)
total_level = self._levels
level = total_level - 1
while level >= 0 and path[level].next.key == key:
path[level].next = path[level].next.next
level -= 1
walk = self.head
while walk is not None:
if walk.next is self.tail:
self._levels -= 1
self.head = walk.down
self.tail = self.tail.down
walk = walk.down
else:
break
self._num_nodes -= 1
if self._levels == 0:
self._add_level()
return return_node

def __str__(self):
node2row = {}
node2col = {}
walk = self.head
curr_level = self._levels - 1
while walk is not None:
curr_node = walk
col = 0
while curr_node is not None:
if curr_node.key != math.inf and curr_node.key != -math.inf:
node2row[curr_node] = curr_level
if walk.down is None:
node2col[curr_node.key] = col
col += 1
curr_node = curr_node.next
walk = walk.down
curr_level -= 1
print(self._num_nodes, self._levels)
sl_mat = [[str(None) for _ in range(self._num_nodes)] for _ in range(self._levels)]
walk = self.head
while walk is not None:
curr_node = walk
while curr_node is not None:
if curr_node in node2row:
row = node2row[curr_node]
col = node2col[curr_node.key]
sl_mat[row][col] = str(curr_node)
curr_node = curr_node.next
walk = walk.down
sl_str = ""
for level_list in sl_mat[::-1]:
for node_str in level_list:
sl_str += node_str + " "
if len(sl_str) > 0:
sl_str += "\n"
return sl_str
53 changes: 48 additions & 5 deletions pydatastructs/linear_data_structures/tests/test_linked_lists.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pydatastructs.linear_data_structures import DoublyLinkedList, SinglyLinkedList, SinglyCircularLinkedList, DoublyCircularLinkedList
from pydatastructs.linear_data_structures import DoublyLinkedList, SinglyLinkedList, SinglyCircularLinkedList, DoublyCircularLinkedList, SkipList
from pydatastructs.utils.raises_util import raises
import copy, random

Expand All @@ -22,7 +22,9 @@ def test_DoublyLinkedList():
assert dll.search(3) == dll[-2]
assert dll.search(-1) is None
dll[-2].key = 0
assert str(dll) == "['7', '5', '1', '6', '1.1', '0', '9']"
assert str(dll) == ("['(7, None)', '(5, None)', '(1, None)', "
"'(6, None)', '(1.1, None)', '(0, None)', "
"'(9, None)']")
assert len(dll) == 7
assert raises(IndexError, lambda: dll.insert_at(8, None))
assert raises(IndexError, lambda: dll.extract(20))
Expand Down Expand Up @@ -57,7 +59,8 @@ def test_SinglyLinkedList():
assert sll.popleft().key == 2
assert sll.popright().key == 6
sll[-2].key = 0
assert str(sll) == "['2', '4', '1', '0', '9']"
assert str(sll) == ("['(2, None)', '(4, None)', '(1, None)', "
"'(0, None)', '(9, None)']")
assert len(sll) == 5
assert raises(IndexError, lambda: sll.insert_at(6, None))
assert raises(IndexError, lambda: sll.extract(20))
Expand Down Expand Up @@ -93,7 +96,8 @@ def test_SinglyCircularLinkedList():
assert scll.popright().key == 6
assert scll.search(-1) is None
scll[-2].key = 0
assert str(scll) == "['2', '4', '1', '0', '9']"
assert str(scll) == ("['(2, None)', '(4, None)', '(1, None)', "
"'(0, None)', '(9, None)']")
assert len(scll) == 5
assert raises(IndexError, lambda: scll.insert_at(6, None))
assert raises(IndexError, lambda: scll.extract(20))
Expand Down Expand Up @@ -130,7 +134,9 @@ def test_DoublyCircularLinkedList():
assert dcll.popleft().key == 2
assert dcll.popright().key == 4
dcll[-2].key = 0
assert str(dcll) == "['7', '5', '1', '6', '1', '0', '9']"
assert str(dcll) == ("['(7, None)', '(5, None)', '(1, None)', "
"'(6, None)', '(1, None)', '(0, None)', "
"'(9, None)']")
assert len(dcll) == 7
assert raises(IndexError, lambda: dcll.insert_at(8, None))
assert raises(IndexError, lambda: dcll.extract(20))
Expand All @@ -148,3 +154,40 @@ def test_DoublyCircularLinkedList():
dcll_copy.extract(index)
assert str(dcll_copy) == "[]"
assert raises(ValueError, lambda: dcll_copy.extract(1))

def test_SkipList():
random.seed(0)
sl = SkipList()
sl.insert(2)
sl.insert(10)
sl.insert(92)
sl.insert(1)
sl.insert(4)
sl.insert(27)
sl.extract(10)
assert str(sl) == ("(1, None) None None None None \n"
"(1, None) None None None None \n"
"(1, None) (2, None) (4, None) (27, None) (92, None) \n")
assert raises(KeyError, lambda: sl.extract(15))
assert sl.search(1) is True
assert sl.search(47) is False

sl = SkipList()

for a in range(0, 20, 2):
sl.insert(a)
assert sl.search(16) is True
for a in range(4, 20, 4):
sl.extract(a)
assert sl.search(10) is True
for a in range(4, 20, 4):
sl.insert(a)
for a in range(0, 20, 2):
sl.extract(a)
assert sl.search(3) is False

li = SkipList()
li.insert(1)
li.insert(2)
assert li.levels == 1
assert li.size == 2
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def test_LinkedListQueue():
q1 = Queue(implementation='linked_list', items = [0, 1])
q1.append(2)
q1.append(3)
assert str(q1) == "['0', '1', '2', '3']"
assert str(q1) == ("['(0, None)', '(1, None)', "
"'(2, None)', '(3, None)']")
assert len(q1) == 4
assert q1.popleft().key == 0
assert q1.popleft().key == 1
Expand Down Expand Up @@ -83,7 +84,7 @@ def test_LinkedListQueue():
q1 = Queue(implementation='linked_list', items = [0, 1], double_ended=True)
q1.appendleft(2)
q1.append(3)
assert str(q1) == "['2', '0', '1', '3']"
assert str(q1) == "['(2, None)', '(0, None)', '(1, None)', '(3, None)']"
assert len(q1) == 4
assert q1.popleft().key == 2
assert q1.pop().key == 3
Expand Down
Loading

0 comments on commit e3586fa

Please sign in to comment.