diff --git a/README.md b/README.md
index 9def01ab..96c2f228 100644
--- a/README.md
+++ b/README.md
@@ -2328,6 +2328,64 @@ In order to achieve greater coverage and encourage more people to contribute to
+
Stack |
diff --git a/src/python/skip_list.py b/src/python/skip_list.py
new file mode 100644
index 00000000..1632a33f
--- /dev/null
+++ b/src/python/skip_list.py
@@ -0,0 +1,471 @@
+"""
+Skip list with:
+ - skip connections made at random
+ - sorted order
+ - removing a node leaves a pseudo dead node
+ - automatically rebuilds with only alive nodes once half of nodes are dead
+ - can be rebuilt whenever desired as well
+ - does not allow for duplicate numbers
+
+"""
+
+import random
+
+
+class Node:
+ def __init__(self, value):
+ self.value = value
+ self._down = None
+
+ # layer number, 1 if on bottom layer
+ self._layer = None
+ self._right = None
+
+ self._isAlive = True
+
+ @property
+ def status(self):
+ return self._isAlive
+
+ @property
+ def kill(self):
+ self._isAlive = False
+
+ @property
+ def next(self):
+ return self._right
+
+ @next.setter
+ def next(self, ptr):
+ self._right = ptr
+
+ @property
+ def get_value(self):
+ return self.value
+
+ @property
+ def layer(self):
+ return self._layer
+
+ @layer.setter
+ def layer(self, val):
+ self._layer = val
+
+ @property
+ def down(self):
+ return self._down
+
+ @down.setter
+ def down(self, ptr):
+ self._down = ptr
+
+
+class Skip_List:
+ def __init__(self) -> None:
+ self.head = None
+
+ # current height of skip list
+ self.height = 1
+
+ # these counts do not consider duplicate elements for skip connections
+ self.size = 0 # number of elements, alive or dead
+ self.num_alive = 0 # number of living elements
+ self.num_dead = 0 # number of dead elements
+
+ def reset(self):
+ # reinitialize values
+ self.head = None
+ self.height = 1
+ self.size = 0
+ self.num_alive = 0
+ self.num_dead = 0
+
+ def status(self):
+ print(
+ f"There are {self.size} element(s) in the skip list, of which "
+ f"{self.num_alive} are alive and {self.num_dead} are dead"
+ )
+ print(f"The list has {self.height} layers")
+
+ def insert(self, value):
+ new = Node(value)
+ # if empty
+ if self.size == 0:
+ # make new node the head
+ self.head = new
+ new.layer = 1
+
+ # if new value is less than head
+ elif value < self.head.get_value:
+ # update head
+ tmp = self.head
+ self.head = new
+ new.layer = tmp.layer
+ new.next = tmp
+ # create skip layers of new head
+ i = tmp.layer
+ if i > 1:
+ curr = tmp.down
+ prev_new = new
+ curr_new = Node(value)
+ curr_new.layer = curr.layer
+ while i > 1:
+ prev_new.down = curr_new
+ curr_new.next = curr
+ curr_new.layer = curr.layer
+
+ curr = curr.down
+ prev_new = curr_new
+ curr_new = Node(value)
+
+ i -= 1
+ else:
+ curr = self.head
+ while True:
+ # move to the right if possible
+ if curr.next is not None:
+ # move to the right if next value is still smaller
+ if curr.next.get_value < value:
+ curr = curr.next
+ # element already exists in the list
+ elif curr.next.get_value == value:
+ print("This value is already in the list")
+ return
+ # if next value is larger you can move down a level
+ elif curr.down is not None:
+ curr = curr.down
+ # otherwise you have found your spot
+ else:
+ tmp = curr.next
+ curr.next = new
+ new.layer = 1
+ new.next = tmp
+ break
+ # move down if you cannot move right
+ elif curr.down is not None:
+ curr = curr.down
+ # otherwise you have reached the end of the list
+ else:
+ curr.next = new
+ new.layer = 1
+ break
+
+ # coin flips to see if new element gets skip connections
+ coin = random.randint(0, 1)
+ if coin:
+ new_layer = 2
+ prev_new = new
+
+ while coin:
+ print(f"Skip connection added on {new_layer}!")
+ curr_new = Node(value)
+
+ # if adding to skip list height
+ if new_layer > self.height:
+ # update head
+ new_head = Node(self.head.get_value)
+ new_head.layer = new_layer
+ new_head.down = self.head
+ self.head = new_head
+ self.height += 1
+
+ curr = self.head
+
+ # start at head and move down to layer that gets new skip connection
+ while curr.layer != new_layer:
+ curr = curr.down
+
+ # move along layer new connection is being added
+ while True:
+ # found spot at end of skip connections
+ if curr.next is None:
+ # insert
+ curr.next = curr_new
+ curr_new.layer = new_layer
+
+ # update curr_new values
+ curr_new.down = prev_new
+
+ # update previous new
+ prev_new = curr_new
+ break
+ # keep moving to the right
+ elif curr.next.get_value < value:
+ curr = curr.next
+ # found skip connection location between values
+ else:
+ # insert between
+ tmp = curr.next
+ curr.next = curr_new
+ curr_new.next = tmp
+
+ # update new node values
+ curr_new.layer = new_layer
+ curr_new.down = prev_new
+
+ # update previous new
+ prev_new = curr_new
+ break
+
+ # flip another coin and increase list level
+ coin = random.randint(0, 1)
+ new_layer += 1
+
+ # update skip list values
+ self.size += 1
+ self.num_alive += 1
+
+ def find(self, value):
+ """
+ returns a pointer to the highest element in skip list with this value
+ if it is alive and exists,
+ otherwise returns none and not considered to be in the list
+
+ """
+ curr = self.head
+ if curr.get_value == value:
+ print(f"{value} is in the list")
+ return curr
+
+ while True:
+ # if you can move right
+ if curr.next is not None:
+ # keep moving right if value not found yet and if it can still be in the list
+ if curr.next.get_value < value:
+ curr = curr.next
+ # if the next value is what you are looking
+ elif curr.next.get_value == value:
+ # check if it is a living node
+ if curr.next.status:
+ print(f"{value} is in the list")
+ return curr.next
+ else:
+ print(f"{value} is not in the list")
+ return None
+ elif curr.down is None:
+ # no need to keep looking all other values are larger and cant go down
+ print(f"{value} is not in the list")
+ return None
+ else:
+ curr = curr.down
+ # move down if you cant move right
+ elif curr.down is not None:
+ curr = curr.down
+ # no where else to go! end of the list
+ else:
+ print(f"{value} is not in the list")
+ return None
+
+ def remove(self, value):
+ """
+ psuedo deletes an element with a value of value if it exists
+ """
+ curr = self.find(value)
+
+ # remove highest occurance of this element
+ if curr is not None:
+ print(f"{value} was removed from the list")
+ self.num_dead += 1
+ self.num_alive -= 1
+ i = curr.layer
+ curr.kill
+ # remove all other occurances of this element
+ while i > 1:
+ curr = curr.down
+ curr.kill
+ i = curr.layer
+
+ # check if rebuild is needed
+ if self.num_dead / self.size > 0.5:
+ self.rebuild()
+
+ else:
+ print(f"{value} was not in the list")
+
+ def show_layers(self, show_type="alive"):
+ """
+ prints out each layer of the skip list
+
+ show_type
+ - 'both' will print out both alive and dead elements
+ - 'alive' will only print alive elements
+ - 'dead' will only print dead elements
+ - treats anything else as 'both'
+ """
+ if self.size == 0:
+ print("The list is empty")
+ return
+
+ if show_type == "alive":
+ print("List contains these living nodes at the following Layers:")
+ elif show_type == "dead":
+ print("List contains these dead nodes at the following Layers:")
+ else:
+ print("List contains these living and dead nodes at the following Layers:")
+
+ # for each layer
+ for curr_layer in range(self.height, 0, -1):
+ curr = self.head
+ vals = [] # values in this layer
+ # move down to layer curr_layer
+ while curr.layer != curr_layer:
+ curr = curr.down
+
+ # add elements until reaching end of this layer
+ while curr is not None:
+ if show_type == "alive":
+ if curr.status:
+ vals.append(curr.get_value)
+ elif show_type == "dead":
+ if not curr.status:
+ vals.append(curr.get_value)
+ else:
+ vals.append(curr.get_value)
+
+ curr = curr.next
+
+ print(f"\tLayer {curr_layer}: {vals}")
+
+ def rebuild(self):
+ """
+ rebuild the data structure with only the alive nodes
+ """
+
+ print("\n\n\nRebuilding Skip List!\n\n\n")
+ vals = []
+
+ # find values of all living nodes
+ curr = self.head
+
+ # move to layer 1
+ while curr.layer != 1:
+ curr = curr.down
+
+ # traverse list adding values of living elements
+ while curr is not None:
+ if curr.status:
+ vals.append(curr.get_value)
+ curr = curr.next
+
+ # reinitalize values of skip list
+ self.reset()
+
+ for val in vals:
+ self.insert(val)
+
+
+def main():
+ L = Skip_List()
+
+ # testing first insert
+ print("\n\n-------- Inserting 3 --------")
+ L.insert(3)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing second insert
+ print("\n\n-------- Inserting 4 --------")
+ L.insert(4)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing insert of new minimum
+ print("\n\n-------- Inserting 1 --------")
+ L.insert(1)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing duplicate insert
+ print("\n\n-------- Inserting Duplicate 3 --------")
+ L.insert(3)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing removal
+ print("\n\n-------- Removing 3 --------")
+ L.remove(3)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing removal of non living element
+ print("\n\n-------- Removing 3 Again--------")
+ L.remove(3)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing removal of element not in list at all
+ print("\n\n-------- Removing 5 (was never inserted) --------")
+ L.remove(5)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing removal to empty and rebuilding
+ print("\n\n-------- Removing Until Empty --------")
+ L.remove(1)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ L.remove(4)
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing arbitrary inserts
+ print("\n\n------------ Arbitrary Inserts ------------\n\n")
+ vals = []
+ for i in range(0, 50):
+ val = random.randint(0, 100)
+ print(f"\n\n---Inserting {val}---")
+ vals.append(val)
+ L.insert(val)
+
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing arbitrary removals
+ print("\n\n------------ Arbitrary Removals ------------\n\n")
+ for i in range(0, 30):
+ idx = random.randint(0, len(vals) - 1)
+ val = vals.pop(idx)
+ print(f"\n\n---Removing {val}---")
+ L.remove(val)
+
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+ # testing arbitary removal of number not in list (dead or alive)
+ print("\n\n------------ Arbitrary Non-Existant Removals ------------\n\n")
+ for i in range(0, 10):
+ val = random.randint(100, 200)
+ print(f"\n\n---Removing {val}---")
+ L.remove(val)
+
+ L.status()
+ L.show_layers()
+ L.show_layers("dead")
+ L.show_layers("both")
+
+
+if __name__ == "__main__":
+ main()
|