Example #1
0
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()