Skip to content

Adding skiplist #328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
136 changes: 134 additions & 2 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 @@ -581,3 +583,133 @@ def extract(self, index):
elif index == 0:
self.tail.next = self.head
return node

class SkipList(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it doesn't inherit LinkedList?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope skip list having a linkedlist implementation will lead to another class of sorted linked list or sorting the linkedlist each insertion and maintaining the layer will obviously need the help of tree like structure, it's better to implement them in separate node which can act same.

"""
Represents Skip List

Examples
========

>>> from pydatastructs import SkipList
>>> sl = SkipList()
>>> sl.insert(6)
>>> sl.insert(1)
>>> sl.insert(3)
>>> repr(sl)
'-inf.->1.->3.->6.->inf.'
>>> sl.remove(1)
>>> sl.insert(4)
>>> sl.insert(2)
>>> sl.search(4)
True
>>> sl.search(10)
False
>>> repr(sl)
'-inf.->2.->3.->4.->6.->inf.'

"""

def __new__(cls):
obj = object.__new__(cls)
obj.head = None
obj.tail = None
obj._addOneLevel()
return obj

def __repr__(self):
li = self.__str__()
return '->'.join(li[-1])

def __vars__(self):
li = self.__str__()
return ' '.join(map(lambda l: '->'.join(l), li))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these extra functions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vars is dynamic if the user want to print the whole skiplist with each layer it can do so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it would be tough to visualize what has been added and removed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall I leave the repr and remove only the vars method


def __str__(self):
li = []
node = self.head
while node:
l = []
no = node
while no:
l.append('{}{}'.format(no.val, '' if no.down else '.'))
no = no.next
li.append(l)
node = node.down
return li

def _addOneLevel(self):
self.tail = SkipNode(math.inf, None, self.tail)
self.head = SkipNode(-math.inf, self.tail, self.head)


def _growUp(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write as _grow_up (snake case).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep will make the changes

return random.getrandbits(1) % 2 == 0

def _search(self, target: int) -> bool:
linodes = []
node = self.head
while node:
if node.next.val >= target:
linodes.append(node)
node = node.down
else:
node = node.next
return linodes

def levels(self):
"""
Returns the number of level in the
Skip list.
"""
r = 0
node = self.head
while node:
r += 1
node = node.down
return r

def search(self, target: int):
"""
Check if the number is present in the
Skiplist. Returns True if present else
return False.
"""
return self._search(target)[-1].next.val == target

def insert(self, num: int):
"""
Adds the given num value into
the SkipList.
"""
linodes = self._search(num)
tip = linodes[-1]
below = SkipNode(num, tip.next)
tip.next = below
totalLevel = len(linodes)
level = 1
while self._growUp() and level <= totalLevel:
if level == totalLevel:
self._addOneLevel()
prev = self.head
else:
prev = linodes[totalLevel - 1 - level]
below = SkipNode(num, prev.next, below)
prev.next = below
level += 1

def remove(self, num: int):
"""
Delete the given num value from
the SkipList. Optional if not present
raises Error
"""
linodes = self._search(num)
tip = linodes[-1]
if tip.next.val != num:
raise ValueError('Value not found.')
totalLevel = len(linodes)
level = totalLevel - 1
while level >= 0 and linodes[level].next.val == num:
linodes[level].next = linodes[level].next.next
level -= 1
41 changes: 40 additions & 1 deletion 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 Down Expand Up @@ -148,3 +148,42 @@ def test_DoublyCircularLinkedList():
dcll_copy.extract(index)
assert str(dcll_copy) == "[]"
assert raises(ValueError, lambda: dcll_copy.extract(1))

def test_SkipList():
sl = SkipList()
f, t = False, True

sl.insert(2)
sl.insert(10)
sl.insert(92)
sl.insert(1)
sl.insert(4)
sl.insert(27)
assert repr(sl) == '-inf.->1.->2.->4.->10.->27.->92.->inf.'
sl.remove(10)
assert repr(sl) == '-inf.->1.->2.->4.->27.->92.->inf.'
assert raises(ValueError, lambda: sl.remove(15))
assert sl.search(1) == t
assert sl.search(47) == f

sl = SkipList()

for a in range(0,20,2):
sl.insert(a)
assert repr(sl) == '-inf.->0.->2.->4.->6.->8.->10.->12.->14.->16.->18.->inf.'
assert sl.search(16) == t
for a in range(4,20,4):
sl.remove(a)
assert repr(sl) == '-inf.->0.->2.->6.->10.->14.->18.->inf.'
assert sl.search(10) == t
for a in range(4,20,4):
sl.insert(a)
for a in range(0,20,2):
sl.remove(a)
assert repr(sl) == '-inf.->inf.'
assert sl.search(3) == f

li = SkipList()
li.insert(1)
li.insert(2)
assert sl.levels() == 4
3 changes: 2 additions & 1 deletion pydatastructs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Set,
CartesianTreeNode,
RedBlackTreeNode,
TrieNode
TrieNode,
SkipNode
)
__all__.extend(misc_util.__all__)
17 changes: 16 additions & 1 deletion pydatastructs/utils/misc_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
'Set',
'CartesianTreeNode',
'RedBlackTreeNode',
'TrieNode'
'TrieNode',
'SkipNode'
]

_check_type = lambda a, t: isinstance(a, t)
Expand Down Expand Up @@ -446,3 +447,17 @@ def _comp(u, v, tcomp):
return False
else:
return tcomp(u, v)

class SkipNode:
def __init__(self, val: int = 0, next = None, down = None):
self.val = val
self.next = next
self.down = down

def __repr__(self):
return self.__str__()

def __str__(self):
return '{}->{}'.format(
self.val,
self.next.val if self.next else 'N')
6 changes: 5 additions & 1 deletion pydatastructs/utils/tests/test_misc_util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pydatastructs.utils import (AdjacencyListGraphNode, AdjacencyMatrixGraphNode,
GraphEdge, BinomialTreeNode, MAryTreeNode, CartesianTreeNode,RedBlackTreeNode)
GraphEdge, BinomialTreeNode, MAryTreeNode, CartesianTreeNode, RedBlackTreeNode, SkipNode)
from pydatastructs.utils.raises_util import raises

def test_AdjacencyListGraphNode():
Expand Down Expand Up @@ -46,3 +46,7 @@ def test_CartesianTreeNode():
def test_RedBlackTreeNode():
c = RedBlackTreeNode(1, 1)
assert str(c) == "(None, 1, 1, None)"

def test_SkipNode():
c = SkipNode(1)
assert str(c) == '1->N'