Module lrng.lrng

Expand source code
from numbers import Number
from copy import copy, deepcopy

class LabeledRange:
    '''
    A helper class for keeping track of the start / stop of a given class in a
    sequence
    '''

    def __init__(self, name:str, start:int, stop:int):
        '''
        Arguments:
            name (str): name of the class
            start (int): the index at which the class starts
            stop (int): the index at which the class stops
        '''
        self.name  = name
        self.start = int(start)
        self.stop  = int(stop)


    '''
    Various conversions from LabeledRange to pythonic types
    '''
    def as_list(self):
        return [self.name, self.start, self.stop]
    def as_str_list(self):
        return [str(e) for e in self.as_list()]
    def as_tuple(self):
        return tuple(self.as_list())
    def as_dict(self):
        return dict(zip(['name', 'start', 'stop'], self.as_list()))
    def as_txt(self, delim='\t', newline='\n', newline_q=True):
        return delim.join(self.as_str_list()) + (newline if newline_q else '')
    def as_csv(self, newline='\n', newline_q=True):
        return self.as_txt(',', newline, newline_q)
    def as_tsv(self, newline='\n', newline_q=True):
        return self.as_txt('\t', newline, newline_q)
    def __hash__(self):
        return hash(self.as_tuple())
    def __repr__(self):
        return '{}{}'.format(self.__class__.__name__, self.as_tuple())
    def __str__(self):
        return self.__repr__()
    def __len__(self):
        return self.stop - self.start
    def __iter__(self):
        return (e for e in self.as_list())
    def __eq__(self, other):
        if not isinstance(other, LabeledRange):
            return False
        return (self.name  == other.name) and \
               (self.start == other.start) and \
               (self.stop  == other.stop)
    def __ne__(self, other):
        return not self.__eq__(other)

    def __contains__(self, other):
        '''
        Arguments:
            other (LabeledRange / int): If other is a LabeledRange, only true
                if other is bounded by self. If other is a number, true if
                self.start <= other <= self.stop
        Returns:
            results (bool)
        '''
        if isinstance(other, Number):
            return self.start <= other <= self.stop
        if not isinstance(other, LabeledRange):
            return False
        if not other.same_q(self):
            return False
        return other.start in self and other.stop in self


    def same_q(self, other):
        '''Whether or not other is of the same class'''
        if not isinstance(other, LabeledRange):
            return False
        return self.name == other.name

    def min(self, other):
        '''Minimum value betwen two ranges'''
        if not self.same_q(other):
            return min([self.start, self.stop])
        return min([self.start, self.stop, other.start, other.stop])

    def max(self, other):
        '''Maximum value betwen two ranges'''
        if not self.same_q(other):
            return max([self.start, self.stop])
        return max([self.start, self.stop, other.start, other.stop])

    def overlap_q(self, other):
        '''Whether or not two ranges overlap'''
        if not self.same_q(other):
            return False
        return any([
            other.start in self, other.stop in self,
            self.start in other, self.stop in other
        ])

    def __add__(self, other):
        '''
        Attempt to combine two ranges together.
        '''
        if not isinstance(other, LabeledRange):
            raise ValueError('{} is not a LabeledRange'.format(other))
        if not self.overlap_q(other):
            return LabeledRanges([deepcopy(self), deepcopy(other)])
        else:
           return LabeledRange(self.name, self.min(other), self.max(other))

    def __iadd__(self, other):
        '''Combine two ranges updating this instance'''
        if self.overlap_q(other):
            self.start = self.min(other)
            self.stop  = self.max(other)
        return self

class LabeledRanges:
    def __init__(self, ranges:list=[]):
        self.ranges = ranges

    def classes(self):
        return set([rng.name for rng in self])
    def as_list(self):
        return [rng.as_list() for rng in self]
    def as_tuple(self):
        return tuple([rng.as_tuple() for rng in self])


    @property
    def ranges(self):
        return self._ranges

    @ranges.setter
    def ranges(self, ranges):
        rngs = []
        for rng in ranges:
            if isinstance(rng, LabeledRange):
                rngs.append(rng)
            else:
                rngs.append(LabeledRange(*rng))
        self._ranges = list(set(rngs))

    @ranges.deleter
    def ranges(self):
        del self._ranges


    def __iter__(self):
        return (rng for rng in self.ranges)

    def __getitem__(self, key):
        return self.ranges[key]

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        s = '{}('.format(self.__class__.__name__)
        if len(self.ranges) == 0:
            return s + ')'
        else:
            s += '\n'
        for i, rng in enumerate(self.ranges):
            s += '\t' + repr(rng) + '\n'
        s += ')'
        return s



    def __eq__(self, other):
        if isinstance(other, LabeledRanges):
            return all([rng in other for rng in self.ranges]) and \
                   all([rng in self for rng in other.ranges])
        return False

    def __ne__(self, other):
        return not self.__eq__(other)


    def __contains__(self, other):
        '''Test if range type is in self or if range is in ranges'''
        if isinstance(other, str):
            # return any([rng.name === other for rng in self])
            for rng in self:
                if rng.name == other:
                    return True
            return False

        if isinstance(other, LabeledRange):
            # return any([other in rng for rng in self])
            for rng in self:
                if other in rng:
                    return True
            return False

        if isinstance(other, LabeledRanges):
            # return all([self.__contains__(rng) for rng in other])
            for rng in other:
                if not self.__contains__(rng):
                    return False
            return True
        return False

    def overlap_q(self, other):
        '''Whether range overlaps with self'''
        for rng in self.ranges:
            if rng.overlap_q(other):
                return True
        return False

    def append(self, other):
        '''Add range to self'''
        # Append a range
        if isinstance(other, LabeledRange):
            found_q = False
            for rng in self:
                if rng.overlap_q(other):
                    found_q = True
                    rng += other
            if not found_q:
                self.ranges.append(other)

        # Map each range to the above block
        if isinstance(other, LabeledRanges):
            for rng in other:
                self.append(other)

        return self


    def __give__(self, other):
        '''Add other to self'''
        if isinstance(other, LabeledRange):
            self.append(other)

        if isinstance(other, LabeledRanges):
            for rng in other:
                self.append(rng)

        return self.simplify()

    def simplify(self):
        '''Remove duplicate ranges'''
        for rng in self:
            self.append(rng)
        self.ranges = list(set(self.ranges))
        return self

    def __add__(self, other):
        cp = deepcopy(self)
        cp.__give__(other)
        return cp

    def __iadd__(self, other):
        self.__give__(other)
        return self

    def __radd__(self, other):
        if not isinstance(other, LabeledRange) or not isinstance(other, LabeledRanges):
            return self
        self.__iadd__(other)
        return self

Classes

class LabeledRange (name: str, start: int, stop: int)

A helper class for keeping track of the start / stop of a given class in a sequence

Arguments

name (str): name of the class start (int): the index at which the class starts stop (int): the index at which the class stops

Expand source code
class LabeledRange:
    '''
    A helper class for keeping track of the start / stop of a given class in a
    sequence
    '''

    def __init__(self, name:str, start:int, stop:int):
        '''
        Arguments:
            name (str): name of the class
            start (int): the index at which the class starts
            stop (int): the index at which the class stops
        '''
        self.name  = name
        self.start = int(start)
        self.stop  = int(stop)


    '''
    Various conversions from LabeledRange to pythonic types
    '''
    def as_list(self):
        return [self.name, self.start, self.stop]
    def as_str_list(self):
        return [str(e) for e in self.as_list()]
    def as_tuple(self):
        return tuple(self.as_list())
    def as_dict(self):
        return dict(zip(['name', 'start', 'stop'], self.as_list()))
    def as_txt(self, delim='\t', newline='\n', newline_q=True):
        return delim.join(self.as_str_list()) + (newline if newline_q else '')
    def as_csv(self, newline='\n', newline_q=True):
        return self.as_txt(',', newline, newline_q)
    def as_tsv(self, newline='\n', newline_q=True):
        return self.as_txt('\t', newline, newline_q)
    def __hash__(self):
        return hash(self.as_tuple())
    def __repr__(self):
        return '{}{}'.format(self.__class__.__name__, self.as_tuple())
    def __str__(self):
        return self.__repr__()
    def __len__(self):
        return self.stop - self.start
    def __iter__(self):
        return (e for e in self.as_list())
    def __eq__(self, other):
        if not isinstance(other, LabeledRange):
            return False
        return (self.name  == other.name) and \
               (self.start == other.start) and \
               (self.stop  == other.stop)
    def __ne__(self, other):
        return not self.__eq__(other)

    def __contains__(self, other):
        '''
        Arguments:
            other (LabeledRange / int): If other is a LabeledRange, only true
                if other is bounded by self. If other is a number, true if
                self.start <= other <= self.stop
        Returns:
            results (bool)
        '''
        if isinstance(other, Number):
            return self.start <= other <= self.stop
        if not isinstance(other, LabeledRange):
            return False
        if not other.same_q(self):
            return False
        return other.start in self and other.stop in self


    def same_q(self, other):
        '''Whether or not other is of the same class'''
        if not isinstance(other, LabeledRange):
            return False
        return self.name == other.name

    def min(self, other):
        '''Minimum value betwen two ranges'''
        if not self.same_q(other):
            return min([self.start, self.stop])
        return min([self.start, self.stop, other.start, other.stop])

    def max(self, other):
        '''Maximum value betwen two ranges'''
        if not self.same_q(other):
            return max([self.start, self.stop])
        return max([self.start, self.stop, other.start, other.stop])

    def overlap_q(self, other):
        '''Whether or not two ranges overlap'''
        if not self.same_q(other):
            return False
        return any([
            other.start in self, other.stop in self,
            self.start in other, self.stop in other
        ])

    def __add__(self, other):
        '''
        Attempt to combine two ranges together.
        '''
        if not isinstance(other, LabeledRange):
            raise ValueError('{} is not a LabeledRange'.format(other))
        if not self.overlap_q(other):
            return LabeledRanges([deepcopy(self), deepcopy(other)])
        else:
           return LabeledRange(self.name, self.min(other), self.max(other))

    def __iadd__(self, other):
        '''Combine two ranges updating this instance'''
        if self.overlap_q(other):
            self.start = self.min(other)
            self.stop  = self.max(other)
        return self

Methods

def as_csv(self, newline='\n', newline_q=True)
Expand source code
def as_csv(self, newline='\n', newline_q=True):
    return self.as_txt(',', newline, newline_q)
def as_dict(self)
Expand source code
def as_dict(self):
    return dict(zip(['name', 'start', 'stop'], self.as_list()))
def as_list(self)
Expand source code
def as_list(self):
    return [self.name, self.start, self.stop]
def as_str_list(self)
Expand source code
def as_str_list(self):
    return [str(e) for e in self.as_list()]
def as_tsv(self, newline='\n', newline_q=True)
Expand source code
def as_tsv(self, newline='\n', newline_q=True):
    return self.as_txt('\t', newline, newline_q)
def as_tuple(self)
Expand source code
def as_tuple(self):
    return tuple(self.as_list())
def as_txt(self, delim='\t', newline='\n', newline_q=True)
Expand source code
def as_txt(self, delim='\t', newline='\n', newline_q=True):
    return delim.join(self.as_str_list()) + (newline if newline_q else '')
def max(self, other)

Maximum value betwen two ranges

Expand source code
def max(self, other):
    '''Maximum value betwen two ranges'''
    if not self.same_q(other):
        return max([self.start, self.stop])
    return max([self.start, self.stop, other.start, other.stop])
def min(self, other)

Minimum value betwen two ranges

Expand source code
def min(self, other):
    '''Minimum value betwen two ranges'''
    if not self.same_q(other):
        return min([self.start, self.stop])
    return min([self.start, self.stop, other.start, other.stop])
def overlap_q(self, other)

Whether or not two ranges overlap

Expand source code
def overlap_q(self, other):
    '''Whether or not two ranges overlap'''
    if not self.same_q(other):
        return False
    return any([
        other.start in self, other.stop in self,
        self.start in other, self.stop in other
    ])
def same_q(self, other)

Whether or not other is of the same class

Expand source code
def same_q(self, other):
    '''Whether or not other is of the same class'''
    if not isinstance(other, LabeledRange):
        return False
    return self.name == other.name
class LabeledRanges (ranges: list = [])
Expand source code
class LabeledRanges:
    def __init__(self, ranges:list=[]):
        self.ranges = ranges

    def classes(self):
        return set([rng.name for rng in self])
    def as_list(self):
        return [rng.as_list() for rng in self]
    def as_tuple(self):
        return tuple([rng.as_tuple() for rng in self])


    @property
    def ranges(self):
        return self._ranges

    @ranges.setter
    def ranges(self, ranges):
        rngs = []
        for rng in ranges:
            if isinstance(rng, LabeledRange):
                rngs.append(rng)
            else:
                rngs.append(LabeledRange(*rng))
        self._ranges = list(set(rngs))

    @ranges.deleter
    def ranges(self):
        del self._ranges


    def __iter__(self):
        return (rng for rng in self.ranges)

    def __getitem__(self, key):
        return self.ranges[key]

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        s = '{}('.format(self.__class__.__name__)
        if len(self.ranges) == 0:
            return s + ')'
        else:
            s += '\n'
        for i, rng in enumerate(self.ranges):
            s += '\t' + repr(rng) + '\n'
        s += ')'
        return s



    def __eq__(self, other):
        if isinstance(other, LabeledRanges):
            return all([rng in other for rng in self.ranges]) and \
                   all([rng in self for rng in other.ranges])
        return False

    def __ne__(self, other):
        return not self.__eq__(other)


    def __contains__(self, other):
        '''Test if range type is in self or if range is in ranges'''
        if isinstance(other, str):
            # return any([rng.name === other for rng in self])
            for rng in self:
                if rng.name == other:
                    return True
            return False

        if isinstance(other, LabeledRange):
            # return any([other in rng for rng in self])
            for rng in self:
                if other in rng:
                    return True
            return False

        if isinstance(other, LabeledRanges):
            # return all([self.__contains__(rng) for rng in other])
            for rng in other:
                if not self.__contains__(rng):
                    return False
            return True
        return False

    def overlap_q(self, other):
        '''Whether range overlaps with self'''
        for rng in self.ranges:
            if rng.overlap_q(other):
                return True
        return False

    def append(self, other):
        '''Add range to self'''
        # Append a range
        if isinstance(other, LabeledRange):
            found_q = False
            for rng in self:
                if rng.overlap_q(other):
                    found_q = True
                    rng += other
            if not found_q:
                self.ranges.append(other)

        # Map each range to the above block
        if isinstance(other, LabeledRanges):
            for rng in other:
                self.append(other)

        return self


    def __give__(self, other):
        '''Add other to self'''
        if isinstance(other, LabeledRange):
            self.append(other)

        if isinstance(other, LabeledRanges):
            for rng in other:
                self.append(rng)

        return self.simplify()

    def simplify(self):
        '''Remove duplicate ranges'''
        for rng in self:
            self.append(rng)
        self.ranges = list(set(self.ranges))
        return self

    def __add__(self, other):
        cp = deepcopy(self)
        cp.__give__(other)
        return cp

    def __iadd__(self, other):
        self.__give__(other)
        return self

    def __radd__(self, other):
        if not isinstance(other, LabeledRange) or not isinstance(other, LabeledRanges):
            return self
        self.__iadd__(other)
        return self

Instance variables

var ranges
Expand source code
@property
def ranges(self):
    return self._ranges

Methods

def append(self, other)

Add range to self

Expand source code
def append(self, other):
    '''Add range to self'''
    # Append a range
    if isinstance(other, LabeledRange):
        found_q = False
        for rng in self:
            if rng.overlap_q(other):
                found_q = True
                rng += other
        if not found_q:
            self.ranges.append(other)

    # Map each range to the above block
    if isinstance(other, LabeledRanges):
        for rng in other:
            self.append(other)

    return self
def as_list(self)
Expand source code
def as_list(self):
    return [rng.as_list() for rng in self]
def as_tuple(self)
Expand source code
def as_tuple(self):
    return tuple([rng.as_tuple() for rng in self])
def classes(self)
Expand source code
def classes(self):
    return set([rng.name for rng in self])
def overlap_q(self, other)

Whether range overlaps with self

Expand source code
def overlap_q(self, other):
    '''Whether range overlaps with self'''
    for rng in self.ranges:
        if rng.overlap_q(other):
            return True
    return False
def simplify(self)

Remove duplicate ranges

Expand source code
def simplify(self):
    '''Remove duplicate ranges'''
    for rng in self:
        self.append(rng)
    self.ranges = list(set(self.ranges))
    return self