packages feed

range-1.0.0.0: Data/Range/Util.hs

{-# LANGUAGE Safe #-}

-- | Internal utility functions shared across the range library.
-- This module is in @other-modules@ and is not part of the public API.
--
-- Functions are grouped by the layer that consumes them:
--   * "Used by Ranges\/Ord" — consumed by the semi-public modules
--   * "Used by Spans\/RangeInternal" — consumed only by the strictly-internal layer
--   * "Util-internal" — building blocks used only within this module
module Data.Range.Util
  ( -- * Used by Ranges and Ord
    compareLower
  , compareHigher
  , invertBound
  , boundsOverlapType
  , pointJoinType
  , boundIsBetween
  , againstLowerBound
  , againstUpperBound
  , takeEvenly
    -- * Used by Spans and RangeInternal
  , compareUpperToLower
  , minBounds
  , maxBounds
  , minBoundsIntersection
  , maxBoundsIntersection
  , insertionSort
  , isEmptySpan
  , removeEmptySpans
  , boundCmp
  , lowestValueInLowerBound
  , highestValueInUpperBound
  ) where

import Data.List (transpose)

import Data.Range.Data

compareLower :: Ord a => Bound a -> Bound a -> Ordering
compareLower ab@(Bound a aType) bb@(Bound b _)
   | ab == bb     = EQ
   | a == b       = if aType == Inclusive then LT else GT
   | a < b        = LT
   | otherwise    = GT

compareHigher :: Ord a => Bound a -> Bound a -> Ordering
compareHigher ab@(Bound a aType) bb@(Bound b _)
   | ab == bb     = EQ
   | a == b       = if aType == Inclusive then GT else LT
   | a < b        = LT
   | otherwise    = GT

-- | Util-internal: used only by 'minBoundsIntersection'.
compareLowerIntersection :: Ord a => Bound a -> Bound a -> Ordering
compareLowerIntersection ab@(Bound a aType) bb@(Bound b _)
   | ab == bb     = EQ
   | a == b       = if aType == Exclusive then LT else GT
   | a < b        = LT
   | otherwise    = GT

-- | Util-internal: used only by 'maxBoundsIntersection'.
compareHigherIntersection :: Ord a => Bound a -> Bound a -> Ordering
compareHigherIntersection ab@(Bound a aType) bb@(Bound b _)
   | ab == bb     = EQ
   | a == b       = if aType == Exclusive then GT else LT
   | a < b        = LT
   | otherwise    = GT

compareUpperToLower :: Ord a => Bound a -> Bound a -> Ordering
compareUpperToLower (Bound upper upperType) (Bound lower lowerType)
   | upper == lower  = if upperType == Inclusive || lowerType == Inclusive then EQ else LT
   | upper < 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 xs ys = go xs ys
   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 (Bound x Inclusive) = Bound x Exclusive
invertBound (Bound x Exclusive) = Bound x Inclusive

isEmptySpan :: Eq a => (Bound a, Bound a) -> Bool
isEmptySpan (Bound a aType, Bound b bType) = a == b && (aType == Exclusive || bType == Exclusive)

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@(ab@(Bound a _), bb@(Bound b _)) r@(xb@(Bound x _), yb@(Bound y _))
   | isEmptySpan l || isEmptySpan r    = Separate
   | a == x                            = Overlap
   | b == y                            = Overlap
   | otherwise = (ab `boundIsBetween` (xb, yb)) `orOverlapType` (xb `boundIsBetween` (ab, bb))

-- | Util-internal: used only by 'boundsOverlapType'.
orOverlapType :: OverlapType -> OverlapType -> OverlapType
orOverlapType Overlap _ = Overlap
orOverlapType _ Overlap = Overlap
orOverlapType Adjoin _ = Adjoin
orOverlapType _ Adjoin = Adjoin
orOverlapType _ _ = Separate

pointJoinType :: BoundType -> BoundType -> OverlapType
pointJoinType Inclusive Inclusive = Overlap
pointJoinType Exclusive Exclusive = 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 ab@(Bound a _) (xb@(Bound x _), yb)
   | boundIsBetween ab (xb, yb) /= Separate = EQ
   | a <= x = LT
   | otherwise = GT

-- | Tests whether a single 'Bound' falls within the span @(lower, upper)@,
-- returning the 'OverlapType' at that point.
--
-- This is the point-in-span primitive. 'boundsOverlapType' is built on top
-- of it and handles the span-vs-span case. Replacing call sites of this
-- function with 'boundsOverlapType' would require constructing a degenerate
-- span @(b, b)@ for each point — see @ai-planning/boundIsBetween-todo.md@
-- for the full analysis.
boundIsBetween :: (Ord a) => Bound a -> (Bound a, Bound a) -> OverlapType
boundIsBetween (Bound a aType) (Bound x xType, Bound y yType)
   | x > a     = Separate
   | x == a    = pointJoinType aType xType
   | a < y     = Overlap
   | a == y    = pointJoinType aType yType
   | otherwise = Separate

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

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

takeEvenly :: [[a]] -> [a]
takeEvenly = concat . transpose

lowestValueInLowerBound :: Enum a => Bound a -> a
lowestValueInLowerBound (Bound a Inclusive) = a
lowestValueInLowerBound (Bound a Exclusive) = succ a

highestValueInUpperBound :: Enum a => Bound a -> a
highestValueInUpperBound (Bound a Inclusive) = a
highestValueInUpperBound (Bound a Exclusive) = pred a