range-1.0.0.0: An efficient and versatile range library.
Safe HaskellSafe
LanguageHaskell2010

Data.Ranges

Description

The primary interface to the range library.

A Range describes a membership set over any Ord type. This module provides the Ranges type — a canonicalised, indexed collection of Range values — along with construction operators, set operations, and membership predicates.

Quick start

Build ranges with the construction operators and combine them with (<>):

>>> (1 +=+ 5 :: Ranges Integer) <> (3 +=+ 8)
Ranges [1 +=+ 8]

Test membership:

>>> inRanges (1 +=+ 10 <> 20 +=+ 30 :: Ranges Integer) 5
True
>>> inRanges (1 +=+ 10 <> 20 +=+ 30 :: Ranges Integer) 15
False

Use mconcat to build from a list:

>>> mconcat [1 +=+ 5, 10 +=+ 15, 12 +=+ 20 :: Ranges Integer]
Ranges [1 +=+ 5,10 +=+ 20]

Transforming ranges

Ranges does not implement Functor. Mapping a function over boundary values is not a well-defined operation for half-infinite ranges: an order-reversing function like negate applied to lbi would need to produce ubi, but Functor cannot express that structural flip.

The idiomatic alternative is to map the query value, not the ranges. Instead of converting boundaries to a new domain, convert incoming queries back to the range's domain:

-- Unit conversion: test a Fahrenheit value against Celsius ranges
let safeTemp = 20 +=+ 37 :: Ranges Double  -- defined in °C
let inSafeTemp f = inRanges safeTemp ((f - 32) * 5 / 9)

This is always correct regardless of whether the conversion is monotone, never requires re-canonicalisation, and avoids the constructor-flip hazard.

Module guide

Synopsis

Core types

data Range a Source #

The Range Data structure; it is capable of representing any type of range. This is the primary data structure in this library. Everything should be possible to convert back into this datatype. All ranges in this structure are inclusively bound.

Constructors

SingletonRange a

Represents a single element as a range. SingletonRange a is equivalent to SpanRange (Bound a Inclusive) (Bound a Inclusive).

SpanRange (Bound a) (Bound a)

Represents a bounded span of elements. The first argument is expected to be less than or equal to the second argument.

LowerBoundRange (Bound a)

Represents a range with a finite lower bound and an infinite upper bound.

UpperBoundRange (Bound a)

Represents a range with an infinite lower bound and a finite upper bound.

InfiniteRange

Represents an infinite range over all values.

Instances

Instances details
Generic (Range a) Source # 
Instance details

Defined in Data.Range.Data

Associated Types

type Rep (Range a) 
Instance details

Defined in Data.Range.Data

type Rep (Range a) = D1 ('MetaData "Range" "Data.Range.Data" "range-1.0.0.0-8IMKDNo4KzPHjxuyrd3j37" 'False) ((C1 ('MetaCons "SingletonRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a)) :+: C1 ('MetaCons "SpanRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a)) :*: S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a)))) :+: (C1 ('MetaCons "LowerBoundRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a))) :+: (C1 ('MetaCons "UpperBoundRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a))) :+: C1 ('MetaCons "InfiniteRange" 'PrefixI 'False) (U1 :: Type -> Type))))

Methods

from :: Range a -> Rep (Range a) x

to :: Rep (Range a) x -> Range a

Show a => Show (Range a) Source # 
Instance details

Defined in Data.Range.Data

Methods

showsPrec :: Int -> Range a -> ShowS

show :: Range a -> String

showList :: [Range a] -> ShowS

NFData a => NFData (Range a) Source # 
Instance details

Defined in Data.Range.Data

Methods

rnf :: Range a -> ()

Eq a => Eq (Range a) Source # 
Instance details

Defined in Data.Range.Data

Methods

(==) :: Range a -> Range a -> Bool

(/=) :: Range a -> Range a -> Bool

Ord a => RangeAlgebra [Range a] Source #

Evaluates to a merged, canonical list of non-overlapping ranges. Used internally by Data.Ranges and useful when you need to inspect individual Range values. Prefer the Ranges instance for general use.

Instance details

Defined in Data.Range.Algebra

type Rep (Range a) Source # 
Instance details

Defined in Data.Range.Data

type Rep (Range a) = D1 ('MetaData "Range" "Data.Range.Data" "range-1.0.0.0-8IMKDNo4KzPHjxuyrd3j37" 'False) ((C1 ('MetaCons "SingletonRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a)) :+: C1 ('MetaCons "SpanRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a)) :*: S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a)))) :+: (C1 ('MetaCons "LowerBoundRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a))) :+: (C1 ('MetaCons "UpperBoundRange" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Bound a))) :+: C1 ('MetaCons "InfiniteRange" 'PrefixI 'False) (U1 :: Type -> Type))))

data Bound a Source #

Represents a bound at a particular value with a BoundType. There is no implicit understanding if this is a lower or upper bound, it could be either.

Constructors

Bound 

Fields

Instances

Instances details
Functor Bound Source # 
Instance details

Defined in Data.Range.Data

Methods

fmap :: (a -> b) -> Bound a -> Bound b

(<$) :: a -> Bound b -> Bound a

Generic (Bound a) Source # 
Instance details

Defined in Data.Range.Data

Associated Types

type Rep (Bound a) 
Instance details

Defined in Data.Range.Data

type Rep (Bound a) = D1 ('MetaData "Bound" "Data.Range.Data" "range-1.0.0.0-8IMKDNo4KzPHjxuyrd3j37" 'False) (C1 ('MetaCons "Bound" 'PrefixI 'True) (S1 ('MetaSel ('Just "boundValue") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a) :*: S1 ('MetaSel ('Just "boundType") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 BoundType)))

Methods

from :: Bound a -> Rep (Bound a) x

to :: Rep (Bound a) x -> Bound a

Show a => Show (Bound a) Source # 
Instance details

Defined in Data.Range.Data

Methods

showsPrec :: Int -> Bound a -> ShowS

show :: Bound a -> String

showList :: [Bound a] -> ShowS

NFData a => NFData (Bound a) Source # 
Instance details

Defined in Data.Range.Data

Methods

rnf :: Bound a -> ()

Eq a => Eq (Bound a) Source # 
Instance details

Defined in Data.Range.Data

Methods

(==) :: Bound a -> Bound a -> Bool

(/=) :: Bound a -> Bound a -> Bool

Ord a => Ord (Bound a) Source # 
Instance details

Defined in Data.Range.Data

Methods

compare :: Bound a -> Bound a -> Ordering

(<) :: Bound a -> Bound a -> Bool

(<=) :: Bound a -> Bound a -> Bool

(>) :: Bound a -> Bound a -> Bool

(>=) :: Bound a -> Bound a -> Bool

max :: Bound a -> Bound a -> Bound a

min :: Bound a -> Bound a -> Bound a

type Rep (Bound a) Source # 
Instance details

Defined in Data.Range.Data

type Rep (Bound a) = D1 ('MetaData "Bound" "Data.Range.Data" "range-1.0.0.0-8IMKDNo4KzPHjxuyrd3j37" 'False) (C1 ('MetaCons "Bound" 'PrefixI 'True) (S1 ('MetaSel ('Just "boundValue") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a) :*: S1 ('MetaSel ('Just "boundType") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 BoundType)))

data BoundType Source #

Represents a type of boundary.

Constructors

Inclusive

The value at the boundary should be included in the bound.

Exclusive

The value at the boundary should be excluded in the bound.

Instances

Instances details
Generic BoundType Source # 
Instance details

Defined in Data.Range.Data

Associated Types

type Rep BoundType 
Instance details

Defined in Data.Range.Data

type Rep BoundType = D1 ('MetaData "BoundType" "Data.Range.Data" "range-1.0.0.0-8IMKDNo4KzPHjxuyrd3j37" 'False) (C1 ('MetaCons "Inclusive" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Exclusive" 'PrefixI 'False) (U1 :: Type -> Type))

Methods

from :: BoundType -> Rep BoundType x

to :: Rep BoundType x -> BoundType

Show BoundType Source # 
Instance details

Defined in Data.Range.Data

Methods

showsPrec :: Int -> BoundType -> ShowS

show :: BoundType -> String

showList :: [BoundType] -> ShowS

NFData BoundType Source # 
Instance details

Defined in Data.Range.Data

Methods

rnf :: BoundType -> ()

Eq BoundType Source # 
Instance details

Defined in Data.Range.Data

Methods

(==) :: BoundType -> BoundType -> Bool

(/=) :: BoundType -> BoundType -> Bool

Ord BoundType Source # 
Instance details

Defined in Data.Range.Data

Methods

compare :: BoundType -> BoundType -> Ordering

(<) :: BoundType -> BoundType -> Bool

(<=) :: BoundType -> BoundType -> Bool

(>) :: BoundType -> BoundType -> Bool

(>=) :: BoundType -> BoundType -> Bool

max :: BoundType -> BoundType -> BoundType

min :: BoundType -> BoundType -> BoundType

type Rep BoundType Source # 
Instance details

Defined in Data.Range.Data

type Rep BoundType = D1 ('MetaData "BoundType" "Data.Range.Data" "range-1.0.0.0-8IMKDNo4KzPHjxuyrd3j37" 'False) (C1 ('MetaCons "Inclusive" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Exclusive" 'PrefixI 'False) (U1 :: Type -> Type))

The Ranges type

data Ranges a Source #

A set of ranges represented as a merged, canonical list of non-overlapping Range values, with pre-built O(log n) membership, O(1) above, and O(1) below predicates.

Construct values with the operators (+=+, lbi, etc.) or with mergeRanges. Combine with (<>) or mconcat.

Semigroup: (<>) computes the set union and merges the result into canonical form.

>>> (1 +=+ 5 :: Ranges Integer) <> (3 +=+ 8)
Ranges [1 +=+ 8]

Monoid: mempty is the empty set. mconcat merges an entire list in a single pass, more efficiently than repeated (<>):

>>> mconcat [1 +=+ 5, 10 +=+ 15, 12 +=+ 20 :: Ranges Integer]
Ranges [1 +=+ 5,10 +=+ 20]

Use unRanges to extract the underlying list.

Instances

Instances details
Ord a => Monoid (Ranges a) Source # 
Instance details

Defined in Data.Ranges

Methods

mempty :: Ranges a

mappend :: Ranges a -> Ranges a -> Ranges a

mconcat :: [Ranges a] -> Ranges a

Ord a => Semigroup (Ranges a) Source # 
Instance details

Defined in Data.Ranges

Methods

(<>) :: Ranges a -> Ranges a -> Ranges a

sconcat :: NonEmpty (Ranges a) -> Ranges a

stimes :: Integral b => b -> Ranges a -> Ranges a

Show a => Show (Ranges a) Source # 
Instance details

Defined in Data.Ranges

Methods

showsPrec :: Int -> Ranges a -> ShowS

show :: Ranges a -> String

showList :: [Ranges a] -> ShowS

NFData a => NFData (Ranges a) Source #

Forces the canonical range list; the cached predicate closure is not forced (it is derived from the list and adds no new thunks).

Instance details

Defined in Data.Ranges

Methods

rnf :: Ranges a -> ()

Eq a => Eq (Ranges a) Source #

Two Ranges values are equal when their canonical range lists are equal.

Instance details

Defined in Data.Ranges

Methods

(==) :: Ranges a -> Ranges a -> Bool

(/=) :: Ranges a -> Ranges a -> Bool

Ord a => RangeAlgebra (Ranges a) Source #

Evaluates a RangeExpr tree whose leaves are Ranges values, producing a canonicalised Ranges with a pre-built membership predicate.

This is the primary evaluation target for user-facing algebra expressions. The implementation converts leaves to [Range a] internally, folds the tree in a single RangeMerge pass (the same efficient path as the [Range a] instance), then wraps the result with mkRanges.

Instance details

Defined in Data.Ranges

Range creation

Each operator constructs a single-element Ranges. Because Ranges is a Semigroup, you can combine them directly with <>:

>>> (1 +=+ 5 :: Ranges Integer) <> (3 +=+ 8)
Ranges [1 +=+ 8]

The operators mirror those in Data.Range.Operators but return Ranges instead of Range, so they compose naturally without wrapping.

(+=+) :: Ord a => a -> a -> Ranges a Source #

Mathematically equivalent to [x, y]. See SpanRange for the underlying constructor.

>>> 1 +=+ 5 :: Ranges Integer
Ranges [1 +=+ 5]

(+=*) :: Ord a => a -> a -> Ranges a Source #

Mathematically equivalent to [x, y).

>>> 1 +=* 5 :: Ranges Integer
Ranges [1 +=* 5]

(*=+) :: Ord a => a -> a -> Ranges a Source #

Mathematically equivalent to (x, y].

>>> 1 *=+ 5 :: Ranges Integer
Ranges [1 *=+ 5]

(*=*) :: Ord a => a -> a -> Ranges a Source #

Mathematically equivalent to (x, y).

>>> 1 *=* 5 :: Ranges Integer
Ranges [1 *=* 5]

lbi :: Ord a => a -> Ranges a Source #

Mathematically equivalent to [x, ∞).

>>> lbi 5 :: Ranges Integer
Ranges [lbi 5]

lbe :: Ord a => a -> Ranges a Source #

Mathematically equivalent to (x, ∞).

ubi :: Ord a => a -> Ranges a Source #

Mathematically equivalent to (−∞, x].

ube :: Ord a => a -> Ranges a Source #

Mathematically equivalent to (−∞, x).

inf :: Ord a => Ranges a Source #

The infinite range, covering all values.

Single-range predicates

inRange :: Ord a => Range a -> a -> Bool Source #

Returns True if the value falls within the single range. Respects Inclusive and Exclusive bounds.

See inRanges for testing against a Ranges collection.

>>> inRange (SpanRange (Bound 1 Inclusive) (Bound 10 Inclusive)) (5 :: Integer)
True
>>> inRange (SpanRange (Bound 1 Inclusive) (Bound 10 Exclusive)) (10 :: Integer)
False

aboveRange :: Ord a => Range a -> a -> Bool Source #

Returns True if the value is strictly above (greater than the upper bound of) the given range.

>>> aboveRange (SpanRange (Bound 1 Inclusive) (Bound 5 Inclusive)) (6 :: Integer)
True
>>> aboveRange (LowerBoundRange (Bound 0 Inclusive)) (6 :: Integer)
False

belowRange :: Ord a => Range a -> a -> Bool Source #

Returns True if the value is strictly below (less than the lower bound of) the given range.

>>> belowRange (SpanRange (Bound 1 Inclusive) (Bound 5 Inclusive)) (0 :: Integer)
True
>>> belowRange (UpperBoundRange (Bound 6 Inclusive)) (0 :: Integer)
False

rangesOverlap :: Ord a => Range a -> Range a -> Bool Source #

Returns True if two ranges share at least one value.

>>> rangesOverlap (SpanRange (Bound 1 Inclusive) (Bound 5 Inclusive)) (SpanRange (Bound 3 Inclusive) (Bound 7 Inclusive) :: Range Integer)
True
>>> rangesOverlap (SpanRange (Bound 1 Inclusive) (Bound 5 Exclusive)) (SpanRange (Bound 5 Inclusive) (Bound 7 Inclusive) :: Range Integer)
False

rangesAdjoin :: Ord a => Range a -> Range a -> Bool Source #

Returns True if two ranges touch at a single exclusive boundary but share no values.

>>> rangesAdjoin (SpanRange (Bound 1 Inclusive) (Bound 5 Exclusive)) (SpanRange (Bound 5 Inclusive) (Bound 7 Inclusive) :: Range Integer)
True
>>> rangesAdjoin (SpanRange (Bound 1 Inclusive) (Bound 5 Inclusive)) (SpanRange (Bound 3 Inclusive) (Bound 7 Inclusive) :: Range Integer)
False

Multi-range predicates

inRanges :: Ord a => Ranges a -> a -> Bool Source #

Returns True if the value falls within any of the given ranges.

The membership predicate is pre-built when the Ranges value is constructed, so each call is O(log n) in 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

aboveRanges :: Ord a => Ranges a -> a -> Bool Source #

Returns True if the value is strictly above all of the given ranges.

This predicate is O(1): the answer is determined by the last element of the canonical range list (which has the largest upper bound), cached at construction time.

>>> aboveRanges (1 +=+ 5 <> 10 +=+ 15 :: Ranges Integer) 20
True
>>> aboveRanges (1 +=+ 5 <> lbi 10 :: Ranges Integer) 20
False

belowRanges :: Ord a => Ranges a -> a -> Bool Source #

Returns True if the value is strictly below all of the given ranges.

This predicate is O(1): the answer is determined by the first element of the canonical range list (which has the smallest lower bound), cached at construction time.

>>> belowRanges (5 +=+ 10 <> 20 +=+ 30 :: Ranges Integer) 1
True
>>> belowRanges (ubi 10 <> 20 +=+ 30 :: Ranges Integer) 1
False

Set operations

mergeRanges :: Ord a => [Range a] -> Ranges a Source #

Canonicalise a raw list of Range values into a Ranges. Overlapping ranges are merged; the result is sorted and non-overlapping.

>>> mergeRanges [LowerBoundRange (Bound 12 Inclusive), SpanRange (Bound 1 Inclusive) (Bound 10 Inclusive), SpanRange (Bound 5 Inclusive) (Bound 15 Inclusive) :: Range Integer]
Ranges [lbi 1]

union :: Ord a => Ranges a -> Ranges a -> Ranges a Source #

Set union. Equivalent to (<>).

>>> union (1 +=+ 10) (5 +=+ 15 :: Ranges Integer)
Ranges [1 +=+ 15]

intersection :: Ord a => Ranges a -> Ranges a -> Ranges a Source #

Set intersection. Returns only values present in both.

>>> intersection (1 +=+ 10) (5 +=+ 15 :: Ranges Integer)
Ranges [5 +=+ 10]

difference :: Ord a => Ranges a -> Ranges a -> Ranges a Source #

Set difference: values in the first Ranges not in the second.

>>> difference (1 +=+ 10) (5 +=+ 15 :: Ranges Integer)
Ranges [1 +=* 5]

invert :: Ord a => Ranges a -> Ranges a Source #

Complement: all values not covered by the given Ranges. invert . invert == id.

>>> invert (1 +=* 10 <> 15 *=+ 20 :: Ranges Integer)
Ranges [ube 1,10 +=+ 15,lbe 20]

Enumerable methods

fromRanges :: (Ord a, Enum a) => Ranges a -> [a] Source #

Instantiate all values covered by the ranges as a list. Warning: not efficient. Prefer inRanges for membership tests. Combine with take to avoid evaluating infinite ranges.

>>> take 5 . fromRanges $ (1 +=+ 10 :: Ranges Integer)
[1,2,3,4,5]
>>> take 6 . fromRanges $ (1 +=+ 3 :: Ranges Integer) <> (10 +=+ 12)
[1,10,2,11,3,12]

joinRanges :: (Ord a, Enum a) => Ranges a -> Ranges a Source #

Join adjacent ranges that are contiguous for Enum types. For example, [1 +=+ 5, 6 +=+ 10] collapses to [1 +=+ 10] for Integer because there is no integer between 5 and 6.

>>> joinRanges (mconcat [1 +=+ 5, 6 +=+ 10] :: Ranges Integer)
Ranges [1 +=+ 10]