packages feed

typed-range-0.1.0.0: Data/Range/Typed/Util.hs

{-# LANGUAGE LambdaCase #-}

module Data.Range.Typed.Util where

import Data.Maybe (mapMaybe)
import Data.Range.Typed.Data
import Optics.Lens (Lens', lens)

-- This module is supposed to contain all of the functions that are required by the rest
-- of the code but could be easily pulled into separate and completely non-related
-- codebases or libraries.

compareLower :: (Ord a) => Bound a -> Bound a -> Ordering
compareLower a b
  | a == b = EQ
  | boundValue a == boundValue b = if boundIsInclusive a then LT else GT
  | boundValue a < boundValue b = LT
  | otherwise = GT

compareHigher :: (Ord a) => Bound a -> Bound a -> Ordering
compareHigher a b
  | a == b = EQ
  | boundValue a == boundValue b = if boundIsInclusive a then GT else LT
  | boundValue a < boundValue b = LT
  | otherwise = GT

compareLowerIntersection :: (Ord a) => Bound a -> Bound a -> Ordering
compareLowerIntersection a b
  | a == b = EQ
  | boundValue a == boundValue b = if boundIsInclusive a then GT else LT
  | boundValue a < boundValue b = LT
  | otherwise = GT

compareHigherIntersection :: (Ord a) => Bound a -> Bound a -> Ordering
compareHigherIntersection a b
  | a == b = EQ
  | boundValue a == boundValue b = if boundIsInclusive a then LT else GT
  | boundValue a < boundValue b = LT
  | otherwise = GT

compareUpperToLower :: (Ord a) => Bound a -> Bound a -> Ordering
compareUpperToLower upper lower
  | boundValue upper == boundValue lower = if boundIsInclusive upper || boundIsInclusive lower then EQ else LT
  | boundValue upper < boundValue lower = LT
  | otherwise = GT

minBounds :: (Ord a) => Bound a -> Bound a -> Bound a
minBounds ao bo = if compareLower ao bo == LT then ao else bo

maxBounds :: (Ord a) => Bound a -> Bound a -> Bound a
maxBounds ao bo = if compareHigher ao bo == GT then ao else bo

minBoundsIntersection :: (Ord a) => Bound a -> Bound a -> Bound a
minBoundsIntersection ao bo = if compareLowerIntersection ao bo == LT then ao else bo

maxBoundsIntersection :: (Ord a) => Bound a -> Bound a -> Bound a
maxBoundsIntersection ao bo = if compareHigherIntersection ao bo == GT then ao else bo

insertionSort :: (a -> a -> Ordering) -> [a] -> [a] -> [a]
insertionSort comp = go
  where
    go (f : fs) (s : ss) = case comp f s of
      LT -> f : go fs (s : ss)
      EQ -> f : s : go fs ss
      GT -> s : go (f : fs) ss
    go [] z = z
    go z [] = z

invertBound :: Bound a -> Bound a
invertBound (InclusiveBound x) = ExclusiveBound x
invertBound (ExclusiveBound x) = InclusiveBound x

isEmptySpan :: (Eq a) => (Bound a, Bound a) -> Bool
isEmptySpan (a, b) = boundValue a == boundValue b && (not (boundIsInclusive a) || not (boundIsInclusive b))

removeEmptySpans :: (Eq a) => [(Bound a, Bound a)] -> [(Bound a, Bound a)]
removeEmptySpans = filter (not . isEmptySpan)

boundsOverlapType :: (Ord a) => (Bound a, Bound a) -> (Bound a, Bound a) -> OverlapType
boundsOverlapType l@(a, b) r@(x, y)
  | isEmptySpan l || isEmptySpan r = Separate
  | boundValue a == boundValue x = Overlap
  | boundValue b == boundValue y = Overlap
  | otherwise = (a `boundIsBetween` (x, y)) `orOverlapType` (x `boundIsBetween` (a, b))

orOverlapType :: OverlapType -> OverlapType -> OverlapType
orOverlapType Overlap _ = Overlap
orOverlapType _ Overlap = Overlap
orOverlapType Adjoin _ = Adjoin
orOverlapType _ Adjoin = Adjoin
orOverlapType _ _ = Separate

pointJoinType :: Bound a -> Bound b -> OverlapType
pointJoinType (InclusiveBound _) (InclusiveBound _) = Overlap
pointJoinType (ExclusiveBound _) (ExclusiveBound _) = Separate
pointJoinType _ _ = Adjoin

-- This function assumes that the bound on the left is a lower bound and that the range is in (lower, upper)
-- bound order
boundCmp :: (Ord a) => Bound a -> (Bound a, Bound a) -> Ordering
boundCmp a (x, y)
  | boundIsBetween a (x, y) /= Separate = EQ
  | boundValue a <= boundValue x = LT
  | otherwise = GT

-- TODO replace everywhere with boundsOverlapType
boundIsBetween :: (Ord a) => Bound a -> (Bound a, Bound a) -> OverlapType
boundIsBetween a (x, y)
  | boundValue x > boundValue a = Separate
  | boundValue x == boundValue a = pointJoinType a x
  | boundValue a < boundValue y = Overlap
  | boundValue a == boundValue y = pointJoinType a y
  | otherwise = Separate

singletonInSpan :: (Ord a) => a -> (Bound a, Bound a) -> OverlapType
singletonInSpan a = boundIsBetween $ InclusiveBound a

againstLowerBound :: (Ord a) => Bound a -> Bound a -> OverlapType
againstLowerBound a lower
  | boundValue lower == boundValue a = pointJoinType a lower
  | boundValue lower < boundValue a = Overlap
  | otherwise = Separate

againstUpperBound :: (Ord a) => Bound a -> Bound a -> OverlapType
againstUpperBound a upper
  | boundValue upper == boundValue a = pointJoinType a upper
  | boundValue a < boundValue upper = Overlap
  | otherwise = Separate

takeEvenly :: [[a]] -> [a]
takeEvenly [] = []
takeEvenly xss = mapMaybe safeHead xss <> takeEvenly (filter (not . null) $ map tail xss)

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x : _) = Just x

pairs :: [a] -> [(a, a)]
pairs [] = []
pairs xs = zip xs (tail xs)

lowestValueInLowerBound :: (Enum a) => Bound a -> a
lowestValueInLowerBound = boundValueNormalized succ

highestValueInUpperBound :: (Enum a) => Bound a -> a
highestValueInUpperBound = boundValueNormalized pred

boundValue :: Bound a -> a
boundValue =
  \case
    InclusiveBound a -> a
    ExclusiveBound a -> a

boundValueNormalized :: (a -> a) -> Bound a -> a
boundValueNormalized normalize =
  \case
    InclusiveBound a -> a
    ExclusiveBound a -> normalize a

boundIsInclusive :: Bound a -> Bool
boundIsInclusive =
  \case
    InclusiveBound _ -> True
    ExclusiveBound _ -> False

-- | Changing `Range`'s lower bound (possibly changing the constructor)
lowerBoundUnstable :: Lens' (AnyRange a) (Maybe (Bound a))
lowerBoundUnstable = lens (\(AnyRangeFor range) -> g range) (\(AnyRangeFor range) -> s range)
  where
    g :: Range hasLowerBound hasUpperBound a -> Maybe (Bound a)
    g =
      \case
        SingletonRange a -> Just $ InclusiveBound a
        SpanRange x _ -> Just x
        LowerBoundRange x -> Just x
        UpperBoundRange _ -> Nothing
        InfiniteRange -> Nothing
        EmptyRange -> Nothing
    s :: Range hasLowerBound hasUpperBound a -> Maybe (Bound a) -> AnyRange a
    s =
      \case
        SingletonRange _ ->
          \case
            Just (InclusiveBound y) -> AnyRangeFor $ SingletonRange y
            Just (ExclusiveBound y) -> AnyRangeFor $ SingletonRange y
            Nothing -> AnyRangeFor EmptyRange
        SpanRange _ x -> maybe (AnyRangeFor $ UpperBoundRange x) (AnyRangeFor . (`SpanRange` x))
        LowerBoundRange _ -> maybe (AnyRangeFor InfiniteRange) (AnyRangeFor . LowerBoundRange)
        UpperBoundRange x -> maybe (AnyRangeFor $ UpperBoundRange x) (AnyRangeFor . (`SpanRange` x))
        InfiniteRange -> maybe (AnyRangeFor InfiniteRange) (AnyRangeFor . LowerBoundRange)
        EmptyRange -> const $ AnyRangeFor EmptyRange

-- | Changing `Range`'s upper bound (possibly changing the constructor)
upperBoundUnstable :: Lens' (AnyRange a) (Maybe (Bound a))
upperBoundUnstable = lens (\(AnyRangeFor range) -> g range) (\(AnyRangeFor range) -> s range)
  where
    g :: Range hasLowerBound hasUpperBound a -> Maybe (Bound a)
    g =
      \case
        SingletonRange a -> Just $ InclusiveBound a
        SpanRange _ x -> Just x
        UpperBoundRange x -> Just x
        LowerBoundRange _ -> Nothing
        InfiniteRange -> Nothing
        EmptyRange -> Nothing
    s :: Range hasLowerBound hasUpperBound a -> Maybe (Bound a) -> AnyRange a
    s =
      \case
        SingletonRange _ ->
          \case
            Just (InclusiveBound y) -> AnyRangeFor $ SingletonRange y
            Just (ExclusiveBound y) -> AnyRangeFor $ SingletonRange y
            Nothing -> AnyRangeFor EmptyRange
        SpanRange x _ -> maybe (AnyRangeFor $ UpperBoundRange x) (AnyRangeFor . SpanRange x)
        UpperBoundRange _ -> maybe (AnyRangeFor InfiniteRange) (AnyRangeFor . UpperBoundRange)
        LowerBoundRange x -> maybe (AnyRangeFor $ LowerBoundRange x) (AnyRangeFor . SpanRange x)
        InfiniteRange -> maybe (AnyRangeFor InfiniteRange) (AnyRangeFor . UpperBoundRange)
        EmptyRange -> const $ AnyRangeFor EmptyRange