class _DHTNode(object): def __init__(self, n=8, ff=0.80, direction=LEFT_TO_RIGHT, parent=None): """ Defines an instance of a new Dynamic Hash Table. :param n: The max number of entries per bucket. :param ff: The fill factor for each bucket. :param direction: The direction to consume the key from. :param parent: A reference to the parent node. """ if n < 3: n = 3 self.n = n if ff < 0.25: ff = 0.25 self.ff = ff if not (direction == LEFT_TO_RIGHT or direction == RIGHT_TO_LEFT): direction = LEFT_TO_RIGHT self.n = n self.ff = ff self.direction = direction self.parent = parent self.left = SortedList(key=extract_key) self.right = SortedList(key=extract_key) def add(self, key, value, bkey): """ Adds a key-value pair to the DHT. :param key: The key to add. :param value: The corresponding value. :param bkey: A binary representation of the key. """ bit, ck = consume_bkey(bkey, self.direction) if bit == LEFT_BIT: if isinstance(self.left, SortedList): self.left.add(_IndexEntry(key, value, ck)) if len(self.left) > self.n * self.ff: self._overflow(LEFT_BIT) elif isinstance(self.left, _DHTNode): self.left.add(key, value, ck) else: raise Exception() elif bit == RIGHT_BIT: if isinstance(self.right, SortedList): self.right.add(_IndexEntry(key, value, ck)) if len(self.right) > self.n * self.ff: self._overflow(RIGHT_BIT) elif isinstance(self.right, _DHTNode): self.right.add(key, value, ck) else: raise Exception() else: raise Exception() def contains(self, key, bkey): """ Determines if the DHT contains at lest one key-value entry with the given key. :param key: The key to lookup. :param bkey: The binary representation of the key. :return: True if at least one key-value entry is found corresponding to the given key, False otherwise. """ bit, ck = consume_bkey(bkey, self.direction) if bit == LEFT_BIT: if isinstance(self.left, SortedList): for entry in self.left: if key == entry.key: return True return False elif isinstance(self.left, _DHTNode): return self.left.contains(key, ck) else: raise Exception() elif bit == RIGHT_BIT: if isinstance(self.right, SortedList): for entry in self.right: if key == entry.key: return True return False elif isinstance(self.right, _DHTNode): return self.right.contains(key, ck) else: raise Exception() else: raise Exception() def delete(self, key, bkey): """ Deletes the first matching key-value entry from the DHT. :param key: The key to lookup. :param bkey: The binary representation of the key. """ bit, ck = consume_bkey(bkey, self.direction) if bit == LEFT_BIT: if isinstance(self.left, SortedList): discard = None for entry in self.left: if key == entry.key: discard = entry break if discard: self.left.discard(discard) elif isinstance(self.left, _DHTNode): self.left.delete(key, ck) else: raise Exception() elif bit == RIGHT_BIT: if isinstance(self.right, SortedList): discard = None for entry in self.right: if key == entry.key: discard = entry break if discard: self.right.discard(discard) elif isinstance(self.right, _DHTNode): self.right.delete(key, ck) else: raise Exception() else: raise Exception() if (isinstance(self.left, SortedList) and not self.left and isinstance(self.right, SortedList) and not self.right): self._underflow() def get(self, key, bkey): """ Gets the first matching key-value entry from the key :param key: The key to lookup. :param bkey: The binary representation of the key. :return: True if at least one key-value entry is found corresponding to the given key, False otherwise. """ bit, ck = consume_bkey(bkey, self.direction) if bit == LEFT_BIT: if isinstance(self.left, SortedList): for entry in self.left: if key == entry.key: return entry.value return None elif isinstance(self.left, _DHTNode): return self.left.get(key, ck) else: raise Exception() elif bit == RIGHT_BIT: if isinstance(self.right, SortedList): for entry in self.right: if key == entry.key: return entry.value return None elif isinstance(self.right, _DHTNode): return self.right.get(key, ck) else: raise Exception() else: raise Exception() def height(self): """ Gets the height of the DHT. :return: The height of the DHT. """ if isinstance(self.left, SortedList) and isinstance( self.right, SortedList): return 1 left = 0 right = 0 if isinstance(self.left, _DHTNode): left = self.left.height() + 1 if isinstance(self.right, _DHTNode): right = self.right.height() + 1 return max(left, right) def traverse(self): """ Traverses the DHT yielding key-value pairs as a Python generator. :return: A Python generator over the key-value pairs in the DHT. """ if isinstance(self.left, SortedList): for entry in self.left: yield entry.key, entry.value elif isinstance(self.left, _DHTNode): yield from self.left.traverse() else: raise Exception() if isinstance(self.right, SortedList): for entry in self.right: yield entry.key, entry.value elif isinstance(self.right, _DHTNode): yield from self.right.traverse() else: raise Exception() def _overflow(self, bit): """ Redistributes the indicated buckets keys to a new left and right bucket. An overflow happens when a bucket grows to large, i.e. its total length is greater than its maximum number of entries times its fill factor. :param bit: The bit representing the buck that overflowed. One of {LEFT_BIT, RIGHT_BIT}. """ if bit == LEFT_BIT and isinstance(self.left, SortedList): new_left = _DHTNode(n=self.n, ff=self.ff, direction=self.direction, parent=self) for entry in self.left: new_left.add(entry.key, entry.value, entry.bkey) self.left.clear() self.left = new_left elif bit == RIGHT_BIT and isinstance(self.right, SortedList): new_right = _DHTNode(n=self.n, ff=self.ff, direction=self.direction, parent=self) for entry in self.right: new_right.add(entry.key, entry.value, entry.bkey) self.right.clear() self.right = new_right else: raise Exception() def _underflow(self): """ Coalesces the two buckets pointed to by this this into a single bucket. An underflow occurs when a deletion causes a state where by both buckets pointed to by this node are empty. This rule is ignored for the root node. """ # only underflow if we are not the root node, root node cannot underflow if self.parent: if self.parent.left == self: self.parent.left = SortedList(key=extract_key) elif self.parent.right == self: self.parent.right = SortedList(key=extract_key) else: raise Exception()