packages feed

range 0.3.2.2 → 0.4.0.0

raw patch · 5 files changed

+113/−45 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Ranges: Ranges :: [Range a] -> Ranges a
- Data.Ranges: [unRanges] :: Ranges a -> [Range a]
- Data.Ranges: newtype Ranges a
+ Data.Ranges: data Ranges a
- Data.Ranges: (*=*) :: a -> a -> Ranges a
+ Data.Ranges: (*=*) :: Ord a => a -> a -> Ranges a
- Data.Ranges: (*=+) :: a -> a -> Ranges a
+ Data.Ranges: (*=+) :: Ord a => a -> a -> Ranges a
- Data.Ranges: (+=*) :: a -> a -> Ranges a
+ Data.Ranges: (+=*) :: Ord a => a -> a -> Ranges a
- Data.Ranges: (+=+) :: a -> a -> Ranges a
+ Data.Ranges: (+=+) :: Ord a => a -> a -> Ranges a
- Data.Ranges: inf :: Ranges a
+ Data.Ranges: inf :: Ord a => Ranges a
- Data.Ranges: lbe :: a -> Ranges a
+ Data.Ranges: lbe :: Ord a => a -> Ranges a
- Data.Ranges: lbi :: a -> Ranges a
+ Data.Ranges: lbi :: Ord a => a -> Ranges a
- Data.Ranges: ube :: a -> Ranges a
+ Data.Ranges: ube :: Ord a => a -> Ranges a
- Data.Ranges: ubi :: a -> Ranges a
+ Data.Ranges: ubi :: Ord a => a -> Ranges a

Files

Data/Range.hs view
@@ -144,7 +144,7 @@ import Data.Range.Data import Data.Range.Operators import Data.Range.Util-import Data.Range.RangeInternal (exportRangeMerge, joinRM, loadRanges)+import Data.Range.RangeInternal (exportRangeMerge, joinRM, loadRanges, RangeMerge(..), buildSpanQuery) import qualified Data.Range.Algebra as Alg  -- | Performs a set union between the two input ranges and returns the resultant set of@@ -276,6 +276,24 @@ -- This is the primary membership test for the library and is significantly more -- performant than approximating it with @'elem' x [lo..hi]@. --+-- The range list is canonicalised and a 'Data.Map'-backed lookup structure is+-- built when this function is partially applied to its range argument. This+-- means that when testing multiple values against the same set of ranges,+-- partial application amortises the setup cost:+--+-- @+-- -- Efficient: map is built once+-- let memberOf = inRanges myRanges+-- filter memberOf largeList+--+-- -- Also fine for one-off checks+-- inRanges myRanges someValue+-- @+--+-- The first argument does not need to be in merged\/canonical form; the+-- function canonicalises it internally. If the input is already canonical+-- (e.g. the result of 'mergeRanges'), canonicalisation is a no-op.+-- -- >>> inRanges [1 +=+ 10, 20 +=+ 30] (5 :: Integer) -- True -- >>> inRanges [1 +=+ 10, 20 +=+ 30] (15 :: Integer)@@ -285,7 +303,10 @@ -- -- See also 'inRange' for testing against a single range. inRanges :: (Ord a) => [Range a] -> a -> Bool-inRanges rs a = any (`inRange` a) rs+inRanges rs =+  case loadRanges rs of+    IRM            -> const True+    RM lb ub spans -> buildSpanQuery lb ub spans  -- | Checks if the value provided is above (or greater than) the biggest value in -- the given range.
Data/Range/RangeInternal.hs view
@@ -1,8 +1,10 @@ {-# LANGUAGE Safe #-}+{-# LANGUAGE BangPatterns #-}  module Data.Range.RangeInternal where  import Data.Maybe (catMaybes)+import qualified Data.Map.Strict as Map  import Data.Range.Data import Data.Range.Spans@@ -250,3 +252,22 @@    (maybe [] (\x -> [RM Nothing (Just x) []]) upper) ++    fmap (\x -> RM Nothing Nothing [x]) spans ++    (maybe [] (\x -> [RM (Just x) Nothing []]) lower)++-- | Pre-build a 'Data.Map'-backed lookup structure from a canonical span list,+-- returning an O(log n) membership predicate. Build the map once; apply the+-- returned function for every subsequent query.+-- Precondition: spans are sorted and non-overlapping (canonical form).+buildSpanQuery :: Ord a+               => Maybe (Bound a)       -- ^ largest lower bound (semi-infinite tail)+               -> Maybe (Bound a)       -- ^ largest upper bound (semi-infinite tail)+               -> [(Bound a, Bound a)]  -- ^ canonical finite spans+               -> (a -> Bool)+buildSpanQuery lb ub spans =+  let !m = Map.fromList spans+  in \val ->+       let v = Bound val Inclusive+       in maybe False (\b -> Overlap == againstUpperBound v b) ub+          || maybe False (\b -> Overlap == againstLowerBound v b) lb+          || case Map.lookupLE v m of+               Nothing       -> False+               Just (lo, hi) -> Overlap == boundIsBetween v (lo, hi)
Data/Range/Util.hs view
@@ -2,7 +2,7 @@  module Data.Range.Util where -import Data.Maybe (catMaybes)+import Data.List (transpose)  import Data.Range.Data @@ -128,12 +128,7 @@    | otherwise    = Separate  takeEvenly :: [[a]] -> [a]-takeEvenly [] = []-takeEvenly xss = (catMaybes . map safeHead $ xss) ++ takeEvenly (filter (not . null) . map tail $ xss)--safeHead :: [a] -> Maybe a-safeHead [] = Nothing-safeHead (x : _) = Just x+takeEvenly = concat . transpose  pairs :: [a] -> [(a, a)] pairs [] = []
Data/Ranges.hs view
@@ -24,7 +24,7 @@ --   expects a 'Monoid' (e.g. 'mconcat', 'fold', writer-style accumulation). module Data.Ranges (   -- * The Ranges type-  Ranges(..),+  Ranges(unRanges),   -- * Range creation   -- $creation   (+=+),@@ -57,6 +57,13 @@ import Data.Semigroup import qualified Data.Range as R +-- | Smart constructor. Canonicalises the range list and pre-builds the cached+-- lookup predicate. All internal paths that produce a 'Ranges' go through this.+mkRanges :: Ord a => [R.Range a] -> Ranges a+mkRanges xs =+  let canonical = R.mergeRanges xs+  in Ranges canonical (Just (R.inRanges canonical))+ -- $creation -- Each operator constructs a single-element 'Ranges'. Because 'Ranges' is a -- 'Semigroup', you can combine them directly with '<>':@@ -85,74 +92,96 @@ -- -- >>> fmap (*2) (1 +=+ 5 :: Ranges Integer) -- Ranges [2 +=+ 10]-newtype Ranges a = Ranges { unRanges :: [R.Range a] }+--+-- Use 'unRanges' to extract the underlying list. Do not construct 'Ranges'+-- directly; use the operators or set-operation functions so that the cached+-- lookup structure is always kept consistent.+data Ranges a = Ranges+  { unRanges     :: [R.Range a]     -- ^ The canonical (sorted, non-overlapping) range list.+  , _rangesQuery :: Maybe (a -> Bool) -- ^ Cached O(log n) predicate; Nothing after 'fmap'.+  }  instance Show a => Show (Ranges a) where-   showsPrec i (Ranges xs) = ((++) "Ranges ") . showsPrec i xs+   showsPrec i r = ((++) "Ranges ") . showsPrec i (unRanges r)  -- | @('<>')@ computes the set union of two 'Ranges' and merges the result into -- canonical (non-overlapping) form. Associative, with 'mempty' as the identity. instance Ord a => Semigroup (Ranges a) where-   (<>) (Ranges a) (Ranges b) = Ranges . R.mergeRanges $ a ++ b+   (<>) a b = mkRanges $ unRanges a ++ unRanges b  -- | 'mempty' is the empty set. 'mconcat' is more efficient than folding '<>' -- because it merges all ranges in a single pass. instance Ord a => Monoid (Ranges a) where-   mempty = Ranges []-   mappend (Ranges a) (Ranges b) = Ranges . R.mergeRanges $ a ++ b-   mconcat = Ranges . R.mergeRanges . concat . fmap unRanges+   mempty = mkRanges []+   mappend a b = mkRanges $ unRanges a ++ unRanges b+   mconcat = mkRanges . concat . fmap unRanges  -- | Maps a function over every boundary value in every range. -- Note that mapping a non-monotonic function can produce ill-formed ranges -- (e.g. a span whose lower bound ends up greater than its upper bound). -- Use with care on ordered types.+--+-- The cached lookup predicate cannot be pre-built here because 'Functor' does+-- not allow an 'Ord' constraint. Calling 'inRanges' on the result will still+-- pre-build the map on partial application via 'Data.Range.inRanges'. instance Functor Ranges where-   fmap f (Ranges xs) = Ranges . fmap (fmap f) $ xs+   fmap f r = Ranges (fmap (fmap f) (unRanges r)) Nothing  -- | Mathematically equivalent to @[x, y]@. See 'R.+=+' for details.-(+=+) :: a -> a -> Ranges a-(+=+) a b = Ranges . pure $ (R.+=+) a b+(+=+) :: Ord a => a -> a -> Ranges a+(+=+) a b = mkRanges . pure $ (R.+=+) a b  -- | Mathematically equivalent to @[x, y)@. See 'R.+=*' for details.-(+=*) :: a -> a -> Ranges a-(+=*) a b = Ranges . pure $ (R.+=*) a b+(+=*) :: Ord a => a -> a -> Ranges a+(+=*) a b = mkRanges . pure $ (R.+=*) a b  -- | Mathematically equivalent to @(x, y]@. See 'R.*=+' for details.-(*=+) :: a -> a -> Ranges a-(*=+) a b = Ranges . pure $ (R.*=+) a b+(*=+) :: Ord a => a -> a -> Ranges a+(*=+) a b = mkRanges . pure $ (R.*=+) a b  -- | Mathematically equivalent to @(x, y)@. See 'R.*=*' for details.-(*=*) :: a -> a -> Ranges a-(*=*) a b = Ranges . pure $ (R.*=*) a b+(*=*) :: Ord a => a -> a -> Ranges a+(*=*) a b = mkRanges . pure $ (R.*=*) a b  -- | Mathematically equivalent to @[x, ∞)@. See 'R.lbi' for details.-lbi :: a -> Ranges a-lbi = Ranges . pure . R.lbi+lbi :: Ord a => a -> Ranges a+lbi = mkRanges . pure . R.lbi  -- | Mathematically equivalent to @(x, ∞)@. See 'R.lbe' for details.-lbe :: a -> Ranges a-lbe = Ranges . pure . R.lbe+lbe :: Ord a => a -> Ranges a+lbe = mkRanges . pure . R.lbe  -- | Mathematically equivalent to @(−∞, x]@. See 'R.ubi' for details.-ubi :: a -> Ranges a-ubi = Ranges . pure . R.ubi+ubi :: Ord a => a -> Ranges a+ubi = mkRanges . pure . R.ubi  -- | Mathematically equivalent to @(−∞, x)@. See 'R.ube' for details.-ube :: a -> Ranges a-ube = Ranges . pure . R.ube+ube :: Ord a => a -> Ranges a+ube = mkRanges . pure . R.ube  -- | The infinite range, covering all values. See 'R.inf' for details.-inf :: Ranges a-inf = Ranges [R.inf]+inf :: Ord a => Ranges a+inf = mkRanges [R.inf]  -- | Returns 'True' if the value falls within any of the given ranges. --+-- The lookup structure is pre-built when the 'Ranges' value is constructed,+-- so each membership test is O(log n) where n is the number of spans.+-- Partial application is idiomatic:+--+-- @+-- let memberOf = inRanges myRanges+-- filter memberOf largeList+-- @+-- -- >>> inRanges (1 +=+ 10 <> 20 +=+ 30 :: Ranges Integer) 5 -- True -- >>> inRanges (1 +=+ 10 <> 20 +=+ 30 :: Ranges Integer) 15 -- False-inRanges :: (Ord a) => Ranges a -> a -> Bool-inRanges (Ranges xs) = R.inRanges xs+inRanges :: Ord a => Ranges a -> a -> Bool+inRanges r = case _rangesQuery r of+  Just f  -> f+  Nothing -> R.inRanges (unRanges r)  -- | Returns 'True' if the value is strictly above (greater than the upper -- bound of) all of the given ranges.@@ -162,7 +191,7 @@ -- >>> aboveRanges (1 +=+ 5 <> lbi 10 :: Ranges Integer) 20 -- False aboveRanges :: (Ord a) => Ranges a -> a -> Bool-aboveRanges (Ranges xs) a = R.aboveRanges xs a+aboveRanges r a = R.aboveRanges (unRanges r) a  -- | Returns 'True' if the value is strictly below (less than the lower -- bound of) all of the given ranges.@@ -172,31 +201,31 @@ -- >>> belowRanges (ubi 10 <> 20 +=+ 30 :: Ranges Integer) 1 -- False belowRanges :: (Ord a) => Ranges a -> a -> Bool-belowRanges (Ranges rs) a = R.belowRanges rs a+belowRanges r a = R.belowRanges (unRanges r) a  -- | Set union of two 'Ranges'. The output is in merged canonical form. -- Equivalent to @('<>')@. union :: (Ord a) => Ranges a -> Ranges a -> Ranges a-union (Ranges a) (Ranges b) = Ranges $ R.union a b+union a b = mkRanges $ R.union (unRanges a) (unRanges b)  -- | Set intersection of two 'Ranges'. Returns only values present in both. -- -- >>> intersection (1 +=+ 10) (5 +=+ 15 :: Ranges Integer) -- Ranges [5 +=+ 10] intersection :: (Ord a) => Ranges a -> Ranges a -> Ranges a-intersection (Ranges a) (Ranges b) = Ranges $ R.intersection a b+intersection a b = mkRanges $ R.intersection (unRanges a) (unRanges b)  -- | Set difference: values in the first 'Ranges' that are not in the second. -- -- >>> difference (1 +=+ 10) (5 +=+ 15 :: Ranges Integer) -- Ranges [1 +=* 5] difference :: (Ord a) => Ranges a -> Ranges a -> Ranges a-difference (Ranges a) (Ranges b) = Ranges $ R.difference a b+difference a b = mkRanges $ R.difference (unRanges a) (unRanges b)  -- | Returns the complement of the given 'Ranges': all values /not/ covered. -- Note that @'invert' . 'invert' == 'id'@. invert :: (Ord a) => Ranges a -> Ranges a-invert = Ranges . R.invert . unRanges+invert = mkRanges . R.invert . unRanges  -- | Instantiates all values covered by the ranges as a list. -- __Warning:__ This is a convenience function and is not efficient. Prefer@@ -215,4 +244,4 @@ -- >>> joinRanges (mconcat [1 +=+ 5, 6 +=+ 10] :: Ranges Integer) -- Ranges [1 +=+ 10] joinRanges :: (Ord a, Enum a) => Ranges a -> Ranges a-joinRanges = Ranges . R.joinRanges . unRanges+joinRanges = mkRanges . R.joinRanges . unRanges
range.cabal view
@@ -10,7 +10,7 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:             0.3.2.2+version:             0.4.0.0  -- A short (one-line) description of the package. synopsis:            An efficient and versatile range library.@@ -82,6 +82,7 @@                   , parsec >= 3 && < 4                   , free >= 4.12 && < 6                   , deepseq >= 1.4 && < 2+                  , containers >= 0.5 && < 1    default-language: Haskell2010   ghc-options: -Wall@@ -127,5 +128,6 @@                   , deepseq >= 1.4 && < 2                   , free >= 4.12 && < 6                   , parsec >= 3 && < 4+                  , containers >= 0.5 && < 1   default-language: Haskell2010   ghc-options:      -Wall -O2 -rtsopts