packages feed

cobot-io-0.1.4.2: src/Bio/Sequence/Range.hs

module Bio.Sequence.Range 
  ( Range (..)
  , Border (..)
  , RangeBorder (..)
  , borderType
  , borderLocation
  , location
  , lower
  , upper
  , before
  , after
  , ranges
  , range
  , checkRange
  , shiftRange
  , mapRange
  , swapRange
  , point
  , preciseSpan
  , between
  , extendRight
  , extendLeft
  , overlap
  , rangeMargins
  ) where

import Control.DeepSeq (NFData)
import Control.Lens    (makeLenses)
import GHC.Generics    (Generic)

-- | The type of range border. A border is @Exceeded@ when its end point is beyond the
-- specified base number, otherwise it is @Precise@.
-- In GenBank, for example, @Exceeded@ borders are marked with < and >.
--
data Border
  = Precise
  | Exceeded
  deriving (Eq, Show, Generic, NFData)

-- | The end point of a range with indication whether it is @Precise@ of @Exceeded@ (see @Border@).
--
data RangeBorder
  = RangeBorder
      { _borderType     :: Border
      , _borderLocation :: Int
      }
  deriving (Eq, Show, Generic, NFData)

makeLenses ''RangeBorder

data Range
  = Point
      { _location :: Int
      }
  -- ^ The exact location of a single base feature
  -- Example in GB:  conf            258
  | Span
      { _lower :: RangeBorder
      , _upper :: RangeBorder
      }
  -- ^ A region consisting of a simple span of bases.
  -- The symbols `<`and `>' are used to indicate that the beginning or end of the
  -- feature is beyond the range of the presented sequence.
  -- Examples in GB: tRNA            1..87
  --                 tRNA            <1..87     
  --                 tRNA            1..>87  
  | Between
      { _before :: Int
      , _after  :: Int
      }
  -- ^ The feature is between bases.
  -- Example in GB:  misc_recomb     105^106
  | Join
      { _ranges :: [Range]
      }
  -- ^ The feature consists of the union of several ranges.
  -- Example in GB:  origin          join(1, 23..50, 77..>100)
  | Complement
      { _range :: Range
      }
  -- ^ Indicates that the range is complementary.
  -- Example in GB:  rep             complement(69..420)
  deriving (Eq, Show, Generic, NFData)

makeLenses ''Range

point :: Int -> Range
point = Point

preciseSpan :: (Int, Int) -> Range
preciseSpan (lo, hi) = Span (RangeBorder Precise lo) (RangeBorder Precise hi)

between :: (Int, Int) -> Range
between = uncurry Between

checkRange :: Int -> Range -> Bool
checkRange len (Point pos) = 0 <= pos && pos < len
checkRange len (Span (RangeBorder _ lInd) (RangeBorder _ rInd)) = lInd < rInd && 0 <= lInd && rInd < len
checkRange len (Between lInd rInd) = lInd < rInd && 0 <= lInd && rInd <= len
checkRange len (Join ranges') = all (checkRange len) ranges'
checkRange len (Complement range') = checkRange len range'

mapRange :: (Int -> Int) -> Range -> Range
mapRange f (Point pos) = Point (f pos)
mapRange f (Span (RangeBorder bLo lo) (RangeBorder bHi hi)) = Span (RangeBorder bLo (f lo)) (RangeBorder bHi (f hi))
mapRange f (Between lo hi) = Between (f lo) (f hi)
mapRange f (Join ranges') = Join $ fmap (mapRange f) ranges'
mapRange f (Complement range') = Complement $ mapRange f range'

shiftRange :: Int -> Range -> Range
shiftRange delta = mapRange (+ delta) 

swapRange :: Range -> Range
swapRange r@Point{}           = r
swapRange (Span brLo brHi)    = Span brHi brLo
swapRange (Between lo hi)     = Between hi lo
swapRange (Join ranges')      = Join $ fmap swapRange ranges'
swapRange (Complement range') = Complement $ swapRange range'

extendRight :: Int -> Range -> Range
extendRight delta (Point a) = Span (RangeBorder Precise a) (RangeBorder Precise (a + delta))
extendRight delta (Span lo (RangeBorder r hi)) = Span lo (RangeBorder r (hi + delta))
extendRight _ b@Between{} = b
extendRight delta (Join ranges') = Join $ extendRight delta <$> ranges'
extendRight delta (Complement range') = Complement $ extendRight delta range'

extendLeft :: Int -> Range -> Range
extendLeft delta (Point a) = Span (RangeBorder Precise (a - delta)) (RangeBorder Precise a)
extendLeft delta (Span (RangeBorder r lo) hi) = Span (RangeBorder r (lo - delta)) hi
extendLeft _ b@Between{} = b
extendLeft delta (Join ranges') = Join $ extendLeft delta <$> ranges'
extendLeft delta (Complement range') = Complement $ extendLeft delta range'

overlap :: Range -> Range -> Bool
overlap (Point a) (Point b) = a == b
overlap (Point a) (Span (RangeBorder _ lo) (RangeBorder _ hi)) = lo <= a && a <= hi
overlap (Point _) (Between _ _) = False

overlap (Span (RangeBorder _ lo1) (RangeBorder _ hi1)) (Span (RangeBorder _ lo2) (RangeBorder _ hi2)) = 
    (lo1 <= lo2 && hi1 >= lo2) ||
    (lo1 >= lo2 && lo1 <= hi2) ||
    (lo1 <= lo2 && hi1 >= hi2)
overlap (Span (RangeBorder _ lo) (RangeBorder _ hi)) (Between b1 b2) = b1 >= lo && b2 <= hi

overlap b1@Between{} b2@Between{} = b1 == b2

overlap r1 (Join ranges') = any (overlap r1) ranges'
overlap r1 (Complement range') = overlap r1 range'

overlap r1 r2 = overlap r2 r1

rangeMargins :: Range -> (Int, Int)
rangeMargins rng = 
    case rng of
      Point x -> (x, x)
      Span (RangeBorder _ lo) (RangeBorder _ hi) -> (lo, hi)
      Between lo hi -> (lo, hi)
      Join children -> let (los, his) = unzip (rangeMargins <$> children) 
                        in (minimum los, maximum his)
      Complement child -> rangeMargins child