Example #1
0
    def add(self, a_range):
        """ Adds a range to the range set. If this range is not connected
        to any current ranges, it will place the new range on its own. If
        there is a connection, any connected ranges will be merged into a single
        range

        Parameters
        ----------
        a_range : A Range object
            The Range to add to the RangeSet

        Raises
        ------
        ValueError
            If adding range of type not compatible with previously
            added ranges
        TypeError:
            If not adding a Range
        """
        if not isinstance(a_range, Range):
            raise TypeError("a_range is not a Range")
        elif a_range.is_empty():
            # Skip if this is an empty range
            return
        # Check for compatibility of types if necessary
        if len(self) > 0:
            if not (issubclass(a_range.lower_cut.the_type,
                               self.ranges[0].lower_cut.the_type) or \
                    issubclass(self.ranges[0].lower_cut.the_type,
                               a_range.lower_cut.the_type)):
                raise ValueError("Range not compatible with previously added ranges")
        # Get the insertion point (where the lower bound should go), should
        # this range be added on its own
        lower_ind = bisect_left(self.lower_cuts, a_range.lower_cut)
        if len(self) == 0:
            # Add on its own if there is nothing in the list
            self.ranges.append(a_range)
            self.lower_cuts.append(a_range.lower_cut)
            self.upper_cuts.append(a_range.upper_cut)
        elif len(self) == lower_ind:
            if not a_range.is_connected(self.ranges[max(lower_ind - 1, 0)]):
                # Add on its own if not connected to previous and last
                self.ranges.insert(lower_ind, a_range)
                self.lower_cuts.insert(lower_ind, a_range.lower_cut)
                self.upper_cuts.insert(lower_ind, a_range.upper_cut)
            else:
                # If connected with the range below, replace with new range
                new_lower_cut = min(a_range.lower_cut,
                                    self.lower_cuts[max(lower_ind - 1, 0)])
                new_upper_cut = max(a_range.upper_cut,
                                    self.upper_cuts[max(lower_ind - 1, 0)])
                new_range = Range(new_lower_cut, new_upper_cut)
                self.ranges[-1] = new_range
                self.lower_cuts[-1] = new_lower_cut
                self.upper_cuts[-1] = new_upper_cut
        elif not any((a_range.is_connected(self.ranges[max(lower_ind - 1, 0)]),
                      a_range.is_connected(self.ranges[lower_ind]))):
            # Add on its own if not connected
            self.ranges.insert(lower_ind, a_range)
            self.lower_cuts.insert(lower_ind, a_range.lower_cut)
            self.upper_cuts.insert(lower_ind, a_range.upper_cut)
        elif a_range.is_connected(self.ranges[max(lower_ind - 1, 0)]):
            # If connected with range below
            new_lower_cut = min(self.lower_cuts[max(lower_ind - 1, 0)],
                                a_range.lower_cut)
            new_upper_cut = max(a_range.upper_cut,
                                self.upper_cuts[max(lower_ind - 1, 0)])
            remove_count = 1
            if len(self) == (lower_ind):
                # If hitting the last range, take the maximum uppercut
                new_upper_cut = max(new_upper_cut, self.upper_cuts[max(lower_ind - 1, 0)])
            else:
                # If not hitting the last range, go find the upper cut
                for i in range(max(1, lower_ind), len(self)):
                    if a_range.is_connected(self.ranges[i]):
                        new_upper_cut = max(new_upper_cut, self.upper_cuts[i])
                        remove_count += 1
                    else:
                        break
            # Make the new range
            new_range = Range(new_lower_cut, new_upper_cut)
            # Get rid of all overlapping ranges
            for i in range(remove_count):
                self.ranges.pop(max(lower_ind - 1, 0))
                self.lower_cuts.pop(max(lower_ind - 1, 0))
                self.upper_cuts.pop(max(lower_ind - 1, 0))
            # Add the new range
            self.ranges.insert(max(lower_ind - 1, 0), new_range)
            self.lower_cuts.insert(max(lower_ind - 1, 0), new_range.lower_cut)
            self.upper_cuts.insert(max(lower_ind - 1, 0), new_range.upper_cut)
        elif a_range.is_connected(self.ranges[lower_ind]):
            # If connected with the range above
            new_lower_cut = min(a_range.lower_cut, self.lower_cuts[lower_ind])
            new_upper_cut = max(a_range.upper_cut, self.upper_cuts[lower_ind])
            remove_count = 0
            if len(self) == (lower_ind + 1):
                # If hitting the last range, you're done
                remove_count += 1
            else:
                # Go find the upper cut
                for i in range(lower_ind, len(self)):
                    if a_range.is_connected(self.ranges[i]):
                        new_upper_cut = max(new_upper_cut, self.upper_cuts[i])
                        remove_count += 1
                    else:
                        break
            # Make the new range
            new_range = Range(new_lower_cut, new_upper_cut)
            # Remove the overlapping ranges
            for i in range(remove_count):
                self.ranges.pop(lower_ind)
                self.lower_cuts.pop(lower_ind)
                self.upper_cuts.pop(lower_ind)
            # Add the new range
            self.ranges.insert(lower_ind, new_range)
            self.lower_cuts.insert(lower_ind, new_range.lower_cut)
            self.upper_cuts.insert(lower_ind, new_range.upper_cut)
Example #2
0
    def remove(self, a_range):
        """ Removes a range from the range set. 

        Parameters
        ----------
        a_range : A Range object
            The Range to remove from the RangeSet

        Raises
        ------
        ValueError
            If removing range of type not compatible with previously
            added ranges
        TypeError
            If not a Range 
        """
        if not isinstance(a_range, Range):
            raise TypeError("a_range is not a Range")
        elif a_range.is_empty():
            # Skip if this is an empty range
            return
        # Check for compatibility of types if necessary
        if len(self) > 0:
            if not (issubclass(a_range.lower_cut.the_type,
                               self.ranges[0].lower_cut.the_type) or issubclass(self.ranges[0].lower_cut.the_type,
                                                                                a_range.lower_cut.the_type)):
                raise ValueError("Range not compatible with previously added ranges")
        # Check if the range actually overlaps with this set
        if not self.overlaps(a_range):
            return
        else:
            # There's some overlap, so deal with that
            # Determine where overlap occurs
            ovlap_lower_ind = max(bisect_left(self.lower_cuts, a_range.lower_cut) - 1, 0)
            ovlap_upper_ind = bisect_left(self.lower_cuts, a_range.upper_cut)
            # Create queue of indices marked for removal
            remove_ranges = deque()
            # Create queue of ranges to add
            add_ranges = deque()
            for i in range(ovlap_lower_ind, ovlap_upper_ind):
                try:
                    # Get intersection of the ranges
                    intersect = a_range.intersection(self.ranges[i])
                    if not intersect.is_empty():
                        if intersect == self.ranges[i]:
                            # Mark range for removal
                            remove_ranges.append(i)
                        elif self.lower_cuts[i] == intersect.lower_cut:
                            # If equal on the left cutpoint, subtract out left
                            # part
                            self.lower_cuts[i] = intersect.upper_cut
                            self.ranges[i] = Range(intersect.upper_cut, self.upper_cuts[i])
                        elif self.upper_cuts[i] == intersect.upper_cut:
                            # If equal on right cutpoint, subtract out right
                            # part
                            self.upper_cuts[i] = intersect.lower_cut
                            self.ranges[i] = Range(self.lower_cuts[i], intersect.lower_cut)
                        else:
                            # If in the middle, split into two parts, putting both into
                            # add queue and placing the old range index into the removal
                            # queue
                            add_ranges.append(Range(self.lower_cuts[i], intersect.lower_cut))
                            add_ranges.append(Range(intersect.upper_cut, self.upper_cuts[i]))
                            remove_ranges.append(i)
                except ValueError:
                    # Continue if no overlap with this range
                    continue
            # Remove any ranges that are marked for removal
            while len(remove_ranges) > 0:
                remove_ind = remove_ranges.pop()
                self.ranges.pop(remove_ind)
                self.lower_cuts.pop(remove_ind)
                self.upper_cuts.pop(remove_ind)
            # Add any ranges that need to be added
            while len(add_ranges) > 0:
                self.add(add_ranges.pop())
Example #3
0
    def difference(self, otherSet):
        """ Creates a new RangeSet in which all elements in another RangeSet
        are taken out of this RangeSet

        Parameters
        ----------
        otherSet : RangeSet object
            The RangeSet used for this difference

        Raises
        ------
        TypeError
            If the object passed in is not a RangeSet
        ValueError
            If the value type of the ranges in the other set not compatible
            with the range's values

        Returns
        -------
        RangeSet consisting of the difference of the two sets
        """
        if not isinstance(otherSet, RangeSet):
            raise TypeError("other_set is not a RangeSet")
        new_set = RangeSet()
        for add_range in self.ranges:
            if otherSet.overlaps(add_range):
                # Determine where overlap occurs
                other_lower_ind = max(bisect_left(otherSet.lower_cuts,
                                                  add_range.lower_cut) - 1, 0)
                other_upper_ind = bisect_left(otherSet.lower_cuts,
                                              add_range.upper_cut)
                new_lower_cut = add_range.lower_cut
                new_upper_cut = add_range.upper_cut
                add = True
                for i in range(other_lower_ind, other_upper_ind):
                    try:
                        # Get the intersection of the ranges
                        intersect = add_range.intersection(otherSet.ranges[i])
                        if not intersect.is_empty():
                            if add_range == intersect:
                                add = False
                                break
                            elif add_range.lower_cut == intersect.lower_cut:
                                # If equal on the left cutpoint, subtract out left
                                # part
                                new_lower_cut = intersect.upper_cut
                                add_range = Range(new_lower_cut, new_upper_cut)
                            elif add_range.upper_cut == intersect.upper_cut:
                                # If equal on right cutpoint, subtract out right
                                # part
                                new_upper_cut = intersect.lower_cut
                                add_range = Range(add_range.lower_cut, new_upper_cut)
                            else:
                                # If in the middle, split into two parts and
                                # add the lower one immediately
                                new_set.add(Range(new_lower_cut,
                                                  intersect.lower_cut))
                                new_lower_cut = intersect.upper_cut
                                new_upper_cut = add_range.upper_cut
                                add_range = Range(new_lower_cut, new_upper_cut)
                    except ValueError:
                        continue
                if add:
                    new_set.add(Range(new_lower_cut, new_upper_cut))
            else:
                new_set.add(add_range)
        return new_set
Example #4
0
    def put(self, key, val):
        """ Creates a mapping from a Range to a value. Note that if the
        key Range overlaps any existing ranges, it will replace those
        Range(s) over the intersection

        Parameters
        ----------
        key : Range object
            A Range to serve as a key
        val : value
            Some value that the Range should map to

        Raises
        ------
        TypeError
            If the key is not a Range object
        """
        if not isinstance(key, Range):
            raise TypeError("key is not a Range")
        elif key.is_empty():
            # Skip if this is an empty range
            return
        # Figure out where to the key/value
        if not self.overlaps(key):
            # If this range is completely on its own, just insert
            insert_ind = bisect_left(self.lower_cuts, key.lower_cut)
            self.ranges.insert(insert_ind, key)
            self.lower_cuts.insert(insert_ind, key.lower_cut)
            self.upper_cuts.insert(insert_ind, key.upper_cut)
            self.items.insert(insert_ind, val)
            return
        else:
            # If this range has some overlap with existing ranges
            ovlap_lower_ind = max(
                bisect_left(self.lower_cuts, key.lower_cut) - 1, 0)
            ovlap_upper_ind = bisect_left(self.lower_cuts, key.upper_cut)
            # Create queue or indices marked for removal
            remove_ranges = deque()
            # Create queue ranges to add
            add_ranges = deque()
            # Create queue of items to add
            add_items = deque()
            for i in range(ovlap_lower_ind, ovlap_upper_ind):
                try:
                    # Get intersection of the ranges
                    intersect = key.intersection(self.ranges[i])
                    if not intersect.is_empty():
                        if intersect == self.ranges[i]:
                            # Mark range for removal
                            remove_ranges.append(i)
                        elif self.lower_cuts[i] == intersect.lower_cut:
                            # If equal on left cutpoint, subtract out left
                            # part
                            self.lower_cuts[i] = intersect.upper_cut
                            self.ranges[i] = Range(intersect.upper_cut,
                                                   self.upper_cuts[i])
                        elif self.upper_cuts[i] == intersect.upper_cut:
                            # If equal on right cutpoint, subtract out
                            # right part
                            self.upper_cuts[i] = intersect.lower_cut
                            self.ranges[i] = Range(self.lower_cuts[i],
                                                   intersect.lower_cut)
                        else:
                            # If in the middle, split into two parts, putting
                            # both in add queue and placing the old range index
                            # in the remove queue
                            add_ranges.append(
                                Range(self.lower_cuts[i], intersect.lower_cut))
                            add_ranges.append(
                                Range(intersect.upper_cut, self.upper_cuts[i]))
                            add_items.append(self.items[i])
                            add_items.append(self.items[i])
                            remove_ranges.append(i)
                except ValueError:
                    # Continue if no overlap with this range
                    continue
            # Remove any ranges that are marked for removal
            while len(remove_ranges) > 0:
                remove_ind = remove_ranges.pop()
                self.ranges.pop(remove_ind)
                self.lower_cuts.pop(remove_ind)
                self.upper_cuts.pop(remove_ind)
                self.items.pop(remove_ind)
            add_items.append(val)
            add_ranges.append(key)
            # Use recursive call to place the pairs, which now
            # should not overlap with any other ranges
            while len(add_ranges) > 0:
                self.put(add_ranges.pop(), add_items.pop())
Example #5
0
    def iteritems(self, start=None, end=None):
        """ Iterates over pairs of (Range, value)

        Parameters
        ----------
        start : comparable, optional
            The starting point for iterating, inclusive
        end : comparable, optional
            The ending point for iterating, inclusive

        Returns
        -------
        Generator of (Range intersecting [start,end], value), ordered by start point
        """
        if start is None:
            start = self.lower_cuts[0]
        else:
            start = Cut.below_value(start)
        if end is None:
            end = self.upper_cuts[-1]
        else:
            end = Cut.above_value(end)
        bounding_range = Range(start, end)
        # Get the bounding indices
        ovlapLowerInd = max(bisect_left(self.lower_cuts, start) - 1, 0)
        ovlapUpperInd = bisect_left(self.lower_cuts, end)
        # Create queue of values that need to be generated
        yield_vals = deque()
        # Create dictionary of values to be generated -> indices containing them
        vals_inds_dict = {}
        for i in range(ovlapLowerInd, ovlapUpperInd):
            # Check if anything can be released from the queue
            while len(yield_vals) > 0:
                if vals_inds_dict[yield_vals[0]][-1] < i - 1:
                    # Yield the full range, value. Remove value from queue
                    val = yield_vals.popleft()
                    yield Range(
                        max(self.lower_cuts[vals_inds_dict[val][0]], start),
                        min(self.upper_cuts[vals_inds_dict[val][-1]],
                            end)), val
                    # Remove value from dict
                    del vals_inds_dict[val]
                else:
                    break
            try:
                # Get intersection of the ranges
                intersect = bounding_range.intersection(self.ranges[i])
                if not intersect.is_empty():
                    # If overlapping with this range, put into queue
                    for val in self.items[i]:
                        if val not in vals_inds_dict:
                            yield_vals.append(val)
                            vals_inds_dict[val] = deque()
                        vals_inds_dict[val].append(i)
            except ValueError:
                # Continue if no overlap with this range
                continue
        ## Yield remaining values
        while len(yield_vals) > 0:
            # Yield the full range, value. Remove value from queue
            val = yield_vals.popleft()
            yield Range(max(self.lower_cuts[vals_inds_dict[val][0]], start),
                        min(self.upper_cuts[vals_inds_dict[val][-1]],
                            end)), val
            # Remove value from dict
            del vals_inds_dict[val]
Example #6
0
    def put(self, key, val):
        """ Creates a mapping from a Range to a value, adding to
        any existing values over that Range

        Parameters
        ----------
        key : Range object
            A Range to serve as a key
        val : value, hashable
            Some value that the Range should map to

        Raises
        ------
        TypeError
            If the key is not a Range object or value is not hashable
        """
        if not isinstance(key, Range):
            raise TypeError("key is not a Range")
        elif not any((isinstance(val, Hashable), self.recurse_add)):
            raise TypeError("value not hashable")
        elif key.is_empty():
            # Skip if this is an empty range
            return
        # Figure out where to the key/value
        if not self.overlaps(key):
            # If this range is completely on its own, just insert
            insert_ind = bisect_left(self.lower_cuts, key.lower_cut)
            self.ranges.insert(insert_ind, key)
            self.lower_cuts.insert(insert_ind, key.lower_cut)
            self.upper_cuts.insert(insert_ind, key.upper_cut)
            if not isinstance(val, set):
                self.items.insert(insert_ind, set([val]))
            else:
                self.items.insert(insert_ind, val)
            return
        else:
            # If this range has some overlap with existing ranges
            ovlap_lower_ind = max(
                bisect_left(self.lower_cuts, key.lower_cut) - 1, 0)
            ovlap_upper_ind = bisect_left(self.lower_cuts, key.upper_cut)
            # Create queue ranges to add
            add_ranges = deque()
            # Create queue of items to add
            add_items = deque()
            # Keep track of next lower cutpoint to add
            next_lower_cut = key.lower_cut
            for i in range(ovlap_lower_ind, ovlap_upper_ind):
                try:
                    # Get intersection of the ranges
                    intersect = key.intersection(self.ranges[i])
                    if not intersect.is_empty():
                        # Add in a Range between the next LowerCut and
                        # the beginning of this intersection if necessary
                        if next_lower_cut < intersect.lower_cut:
                            add_ranges.append(
                                Range(next_lower_cut, intersect.lower_cut))
                            add_items.append(val)
                            next_lower_cut = intersect.lower_cut
                        if intersect == self.ranges[i]:
                            ## If key encompassing existing Range ##
                            # Add item to this range
                            self.items[i].add(val)
                            # Change the next lower cut
                            next_lower_cut = intersect.upper_cut
                        elif self.lower_cuts[i] == intersect.lower_cut:
                            ## If key upper cutpoint enclosed by existing Range ##
                            # Add in the rest of the original Range
                            if self.upper_cuts[i] > intersect.upper_cut:
                                add_ranges.append(
                                    Range(intersect.upper_cut,
                                          self.upper_cuts[i]))
                                add_items.append(set(self.items[i]))
                            # Define original part to be shorter
                            self.upper_cuts[i] = intersect.upper_cut
                            self.ranges[i] = Range(self.lower_cuts[i],
                                                   intersect.upper_cut)
                            self.items[i].add(val)
                            # Change the next lower cut
                            next_lower_cut = intersect.upper_cut
                        elif self.upper_cuts[i] == intersect.upper_cut:
                            ## If key lower cutpoint enclosed by existing Range ##
                            # Add in the rest of the original Range
                            if intersect.lower_cut > self.lower_cuts[i]:
                                add_ranges.append(
                                    Range(self.lower_cuts[i],
                                          intersect.lower_cut))
                                add_items.append(set(self.items[i]))
                            # Define original part to be shorter
                            self.lower_cuts[i] = intersect.lower_cut
                            self.ranges[i] = Range(self.lower_cuts[i],
                                                   intersect.upper_cut)
                            self.items[i].add(val)
                            # Change the next lower cut
                            next_lower_cut = intersect.upper_cut
                        else:
                            # If entire key enclosed by existing Range
                            # Add in lower part of original Range
                            add_ranges.append(
                                Range(self.lower_cuts[i], intersect.lower_cut))
                            add_items.append(set(self.items[i]))
                            # Add in upper part of original Range
                            add_ranges.append(
                                Range(intersect.upper_cut, self.upper_cuts[i]))
                            add_items.append(set(self.items[i]))
                            # Define original part to be middle
                            self.lower_cuts[i] = intersect.lower_cut
                            self.upper_cuts[i] = intersect.upper_cut
                            self.ranges[i] = Range(intersect.lower_cut,
                                                   intersect.upper_cut)
                            self.items[i].add(val)
                            # Change the next lower cut
                            next_lower_cut = intersect.upper_cut
                except ValueError:
                    # Continue if no overlap with this range
                    continue
            # Put in a last range if necessary
            if next_lower_cut < key.upper_cut:
                add_ranges.append(Range(next_lower_cut, key.upper_cut))
                add_items.append(val)
            # Use recursive call to place the pairs, which now
            # should not overlap with any other ranges
            self.recurse_add = True
            while len(add_ranges) > 0:
                self.put(add_ranges.pop(), add_items.pop())
            self.recurse_add = False