diff --git a/.gitignore b/.gitignore index bfb5886..802b023 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .venv __pycache__ +venv \ No newline at end of file diff --git a/Sprint-2/implement_skip_list/skip_list.py b/Sprint-2/implement_skip_list/skip_list.py new file mode 100644 index 0000000..07c5b98 --- /dev/null +++ b/Sprint-2/implement_skip_list/skip_list.py @@ -0,0 +1,94 @@ +import random + +class SkipListNode: + def __init__(self, value, level): + self.value = value + self.next = [None] * (level + 1) # Next nodes at each level + +class SkipList: + def __init__(self): + self.max_level = 16 + self.head = SkipListNode(None, self.max_level) # Head node + self.level = 0 # Current highest level + self.size = 0 + + def _random_level(self): + """Generate random level for a new node (coin flip)""" + level = 0 + while random.random() < 0.5 and level < self.max_level: + level += 1 + return level + + def __contains__(self, key): + """Support 'in' operator""" + return self.contains(key) + + def contains(self, key): + """Check if key exists in the skip list""" + current = self.head + + # Start from highest level and move down + for i in range(self.level, -1, -1): + while current.next[i] and current.next[i].value < key: + current = current.next[i] + + # Move to the node that might contain the value + current = current.next[0] + return current is not None and current.value == key + + def insert(self, value): + """Insert a value into the skip list""" + update = [None] * (self.max_level + 1) + current = self.head + + # Find the position to insert and track update pointers + for i in range(self.level, -1, -1): + while current.next[i] and current.next[i].value < value: + current = current.next[i] + update[i] = current + + current = current.next[0] + + # If value already exists, we'll still insert it (allow duplicates) + new_level = self._random_level() + + # Update the max level if needed + if new_level > self.level: + for i in range(self.level + 1, new_level + 1): + update[i] = self.head + self.level = new_level + + # Create new node + new_node = SkipListNode(value, new_level) + + # Update pointers at each level + for i in range(new_level + 1): + new_node.next[i] = update[i].next[i] + update[i].next[i] = new_node + + self.size += 1 + + def to_list(self): + """Convert skip list to a sorted list""" + result = [] + current = self.head.next[0] # Start from first element at level 0 + + while current: + result.append(current.value) + current = current.next[0] + + return result + + def __str__(self): + """String representation for debugging""" + result = [] + for level in range(self.level, -1, -1): + level_str = f"Level {level}: " + current = self.head.next[level] + elements = [] + while current: + elements.append(str(current.value)) + current = current.next[level] + level_str += " -> ".join(elements) if elements else "empty" + result.append(level_str) + return "\n".join(result) \ No newline at end of file diff --git a/Sprint-2/implement_skip_list/skip_list_test.py b/Sprint-2/implement_skip_list/skip_list_test.py index d20b102..ff10561 100644 --- a/Sprint-2/implement_skip_list/skip_list_test.py +++ b/Sprint-2/implement_skip_list/skip_list_test.py @@ -26,6 +26,51 @@ def test_general_usage(self): self.assertEqual(sl.to_list(), [1, 2, 3, 4, 5, 10]) + def test_duplicate_values(self): + """Test that duplicate values are handled correctly""" + sl = SkipList() + + # Insert duplicates + sl.insert(5) + sl.insert(2) + sl.insert(5) + sl.insert(2) + sl.insert(5) + + # All duplicates should be in the list and sorted + self.assertEqual(sl.to_list(), [2, 2, 5, 5, 5]) + + # Check membership for duplicates + self.assertIn(2, sl) + self.assertIn(5, sl) + + # Verify non-existent values + self.assertNotIn(1, sl) + self.assertNotIn(3, sl) + self.assertNotIn(6, sl) + + def test_large_random_inserts(self): + """Test with larger dataset and random insertion order""" + sl = SkipList() + + # Insert numbers in random order + numbers = [7, 3, 9, 1, 8, 2, 6, 4, 10, 5] + for num in numbers: + sl.insert(num) + + # Should be sorted regardless of insertion order + self.assertEqual(sl.to_list(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + + # Verify all numbers are found + for num in range(1, 11): + self.assertIn(num, sl) + + # Verify numbers outside range are not found + self.assertNotIn(0, sl) + self.assertNotIn(11, sl) + self.assertNotIn(15, sl) + + if __name__ == "__main__": unittest.main()