javelin-0.1.2.0: src/Data/Series/Generic/Definition.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Series.Generic.Definition (
Series(..),
convert,
-- * Basic interface
singleton,
headM, lastM, map, mapWithKey, mapIndex, concatMap, fold, foldM,
foldWithKey, foldMWithKey, foldMap, bifoldMap, foldMapWithKey,
length, null, take, takeWhile, drop, dropWhile,
mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_,
traverseWithKey,
fromIndex,
-- * Conversion to/from Series
IsSeries(..),
-- ** Conversion to/from Maps
fromStrictMap,
toStrictMap,
fromLazyMap,
toLazyMap,
-- ** Conversion to/from list
fromList,
toList,
-- *** Unsafe construction
fromDistinctAscList,
-- ** Conversion to/from vectors
fromVector,
toVector,
-- *** Unsafe construction
fromDistinctAscVector,
-- ** Handling duplicates
Occurrence, fromListDuplicates, fromVectorDuplicates,
-- * Displaying 'Series'
display, displayWith,
noLongerThan,
DisplayOptions(..), defaultDisplayOptions
) where
import Control.DeepSeq ( NFData(rnf) )
import Control.Foldl ( Fold(..), FoldM(..) )
import Control.Monad.ST ( runST )
import Data.Bifoldable ( Bifoldable )
import qualified Data.Bifoldable as Bifoldable
import qualified Data.Foldable as Foldable
import Data.Foldable.WithIndex ( FoldableWithIndex(..))
import Data.Function ( on )
import Data.Functor.WithIndex ( FunctorWithIndex(imap) )
import Data.IntMap.Strict ( IntMap )
import qualified Data.IntMap.Strict as IntMap
import qualified Data.List as List
import qualified Data.Map.Lazy as ML
import Data.Map.Strict ( Map )
import qualified Data.Map.Strict as MS
import Data.Sequence ( Seq )
import qualified Data.Sequence as Seq
import Data.Semigroup ( Semigroup(..) )
import Data.Series.Index ( Index )
import qualified Data.Series.Index as Index
import qualified Data.Series.Index.Internal as Index.Internal
import Data.Set ( Set )
import qualified Data.Set as Set
import Data.Traversable.WithIndex ( TraversableWithIndex(..) )
import qualified Data.Vector as Boxed
import Data.Vector.Algorithms.Intro ( sortUniqBy, sortBy )
import Data.Vector.Generic ( Vector )
import qualified Data.Vector.Generic as Vector
import qualified Data.Vector.Generic.Mutable as GM
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as UM
import Prelude hiding ( take, takeWhile, drop, dropWhile, map, concatMap, foldMap, sum, length, null )
import qualified Prelude as P
-- | A @Series v k a@ is a labeled array of type @v@ filled with values of type @a@,
-- indexed by keys of type @k@.
--
-- Like 'Data.Map.Strict.Map', they support efficient:
--
-- * random access by key ( \(O(\log n)\) );
-- * slice by key ( \(O(\log n)\) ).
--
-- Like 'Data.Vector.Vector', they support efficient:
--
-- * random access by index ( \(O(1)\) );
-- * slice by index ( \(O(1)\) );
-- * numerical operations.
--
data Series v k a
-- The reason the index is a set of keys is that we *want* keys to be ordered.
-- This allows for efficient slicing of the underlying values, because
-- if @k1 < k2@, then the values are also at indices @ix1 < ix2@.
= MkSeries { index :: Index k -- ^ The 'Index' of a series, which contains its (unique) keys in ascending order.
, values :: v a -- ^ The values of a series, in the order of its (unique) keys.
}
-- | \(O(n)\) Convert between two types of 'Series'.
convert :: (Vector v1 a, Vector v2 a) => Series v1 k a -> Series v2 k a
{-# INLINABLE convert #-}
convert (MkSeries ix vs) = MkSeries ix $ Vector.convert vs
-- | \(O(1)\) Create a 'Series' with a single element.
singleton :: Vector v a => k -> a -> Series v k a
{-# INLINABLE singleton #-}
singleton k v = MkSeries (Index.singleton k) $ Vector.singleton v
-- | \(O(n)\) Generate a 'Series' by mapping every element of its index.
fromIndex :: (Vector v a)
=> (k -> a) -> Index k -> Series v k a
{-# INLINABLE fromIndex #-}
fromIndex f ix = MkSeries ix $ Vector.convert
$ Boxed.map f -- Using boxed vector to prevent a (Vector v k) constraint
$ Index.toAscVector ix
-- | The 'IsSeries' typeclass allow for ad-hoc definition
-- of conversion functions, converting to / from 'Series'.
class IsSeries t v k a where
-- | Construct a 'Series' from some container of key-values pairs. There is no
-- condition on the order of pairs. Duplicate keys are silently dropped. If you
-- need to handle duplicate keys, see 'fromListDuplicates' or 'fromVectorDuplicates'.
toSeries :: t -> Series v k a
-- | Construct a container from key-value pairs of a 'Series'.
-- The elements are returned in ascending order of keys.
fromSeries :: Series v k a -> t
instance (Ord k, Vector v a) => IsSeries [(k, a)] v k a where
-- | Construct a series from a list of key-value pairs. There is no
-- condition on the order of pairs.
--
-- >>> let xs = toSeries [('b', 0::Int), ('a', 5), ('d', 1) ]
-- >>> xs
-- index | values
-- ----- | ------
-- 'a' | 5
-- 'b' | 0
-- 'd' | 1
--
-- If you need to handle duplicate keys, take a look at `fromListDuplicates`.
toSeries :: [(k, a)] -> Series v k a
toSeries = toSeries . MS.fromList
{-# INLINABLE toSeries #-}
-- | Construct a list from key-value pairs. The elements are in order sorted by key:
--
-- >>> let xs = Series.toSeries [ ('b', 0::Int), ('a', 5), ('d', 1) ]
-- >>> xs
-- index | values
-- ----- | ------
-- 'a' | 5
-- 'b' | 0
-- 'd' | 1
-- >>> fromSeries xs
-- [('a',5),('b',0),('d',1)]
fromSeries :: Series v k a -> [(k, a)]
fromSeries (MkSeries ks vs)= zip (Index.toAscList ks) (Vector.toList vs)
{-# INLINABLE fromSeries #-}
-- | Construct a 'Series' from a list of key-value pairs. There is no
-- condition on the order of pairs. Duplicate keys are silently dropped. If you
-- need to handle duplicate keys, see 'fromListDuplicates'.
fromList :: (Vector v a, Ord k) => [(k, a)] -> Series v k a
{-# INLINABLE fromList #-}
fromList = toSeries
-- | \(O(n)\) Build a 'Series' from a list of pairs, where the first elements of the pairs (the keys)
-- are distinct elements in ascending order. The precondition that the keys be unique and sorted is not checked.
fromDistinctAscList :: (Vector v a) => [(k, a)] -> Series v k a
fromDistinctAscList xs
= let (!ks, !vs) = unzip xs
in MkSeries (Index.Internal.fromDistinctAscList ks) (Vector.fromListN (List.length vs) vs)
-- | Integer-like, non-negative number that specifies how many occurrences
-- of a key is present in a 'Series'.
--
-- The easiest way to convert from an 'Occurrence' to another integer-like type
-- is the 'fromIntegral' function.
newtype Occurrence = MkOcc Int
deriving (Eq, Enum, Num, Ord, Integral, Real)
deriving newtype (Show, U.Unbox)
-- Occurrence needs to be an 'U.Unbox' type
-- so that 'fromVectorDuplicates' works with unboxed vectors
-- and series.
newtype instance UM.MVector s Occurrence = MV_Occ (UM.MVector s Int)
newtype instance U.Vector Occurrence = V_Occ (U.Vector Int)
deriving instance GM.MVector UM.MVector Occurrence
deriving instance Vector U.Vector Occurrence
-- | Construct a series from a list of key-value pairs.
-- Contrary to 'fromList', values at duplicate keys are preserved. To keep each
-- key unique, an 'Occurrence' number counts up.
fromListDuplicates :: (Vector v a, Ord k) => [(k, a)] -> Series v (k, Occurrence) a
{-# INLINABLE fromListDuplicates #-}
fromListDuplicates = convert . fromVectorDuplicates . Boxed.fromList
-- | Construct a list from key-value pairs. The elements are in order sorted by key.
toList :: Vector v a => Series v k a -> [(k, a)]
{-# INLINABLE toList #-}
toList (MkSeries ks vs) = zip (Index.toAscList ks) (Vector.toList vs)
instance (Ord k) => IsSeries (Boxed.Vector (k, a)) Boxed.Vector k a where
toSeries = fromVector
{-# INLINABLE toSeries #-}
fromSeries = toVector
{-# INLINABLE fromSeries #-}
instance (Ord k, U.Unbox a, U.Unbox k) => IsSeries (U.Vector (k, a)) U.Vector k a where
toSeries :: U.Vector (k, a) -> Series U.Vector k a
toSeries = fromVector
{-# INLINABLE toSeries #-}
fromSeries :: Series U.Vector k a -> U.Vector (k, a)
fromSeries = toVector
{-# INLINABLE fromSeries #-}
-- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no
-- condition on the order of pairs. Duplicate keys are silently dropped. If you
-- need to handle duplicate keys, see 'fromVectorDuplicates'.
--
-- Note that due to differences in sorting,
-- 'Series.fromList' and @'Series.fromVector' . 'Vector.fromList'@
-- may not be equivalent if the input list contains duplicate keys.
fromVector :: (Ord k, Vector v k, Vector v a, Vector v (k, a))
=> v (k, a) -> Series v k a
{-# INLINABLE fromVector #-}
fromVector vec = let (indexVector, valuesVector) = Vector.unzip $ runST $ do
mv <- Vector.thaw vec
-- Note that we're using this particular flavor of `sortUniqBy`
-- because it both sorts AND removes duplicate keys
destMV <- sortUniqBy (compare `on` fst) mv
v <- Vector.freeze destMV
pure (Vector.force v)
in MkSeries (Index.Internal.fromDistinctAscVector indexVector) valuesVector
-- | \(O(n)\) Build a 'Series' from a vector of pairs, where the first elements of the pairs (the keys)
-- are distinct elements in ascending order. The precondition that the keys be unique and sorted is not checked.
fromDistinctAscVector :: (Vector v k, Vector v a, Vector v (k, a))
=> v (k, a) -> Series v k a
fromDistinctAscVector xs
= let (ks, vs) = Vector.unzip xs
in MkSeries (Index.Internal.fromDistinctAscVector ks) vs
-- | Construct a 'Series' from a 'Vector' of key-value pairs, where there may be duplicate keys.
-- There is no condition on the order of pairs.
fromVectorDuplicates :: (Ord k, Vector v k, Vector v a, Vector v (k, a), Vector v (k, Occurrence))
=> v (k, a) -> Series v (k, Occurrence) a
{-# INLINABLE fromVectorDuplicates #-}
fromVectorDuplicates vec
= let (indexVector, valuesVector)
= Vector.unzip $ runST $ do
mv <- Vector.thaw vec
sortBy (compare `on` fst) mv
v <- Vector.freeze mv
pure (Vector.force v)
in MkSeries (Index.Internal.fromDistinctAscVector (occurences indexVector)) valuesVector
where
occurences vs
| Vector.null vs = Vector.empty
| Vector.length vs == 1 = Vector.map (,0) vs
| otherwise = Vector.scanl f (Vector.head vs, 0) (Vector.tail vs)
where
f (lastKey, lastOcc) newKey
| lastKey == newKey = (newKey, lastOcc + 1)
| otherwise = (newKey, 0)
-- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key.
toVector :: (Vector v a, Vector v k, Vector v (k, a))
=> Series v k a -> v (k, a)
{-# INLINABLE toVector #-}
toVector (MkSeries ks vs) = Vector.zip (Index.toAscVector ks) vs
instance (Vector v a) => IsSeries (Map k a) v k a where
toSeries :: Map k a -> Series v k a
toSeries mp = MkSeries
{ index = Index.fromSet $ MS.keysSet mp
, values = Vector.fromListN (MS.size mp) $ MS.elems mp
}
{-# INLINABLE toSeries #-}
fromSeries :: Series v k a -> Map k a
fromSeries (MkSeries ks vs)
= MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)
{-# INLINABLE fromSeries #-}
toLazyMap :: (Vector v a) => Series v k a -> Map k a
{-# INLINABLE toLazyMap #-}
toLazyMap = fromSeries
-- | Construct a series from a lazy 'Data.Map.Lazy.Map'.
fromLazyMap :: (Vector v a) => ML.Map k a -> Series v k a
{-# INLINABLE fromLazyMap #-}
fromLazyMap = toSeries
-- | Convert a series into a strict 'Data.Map.Strict.Map'.
toStrictMap :: (Vector v a) => Series v k a -> Map k a
{-# INLINABLE toStrictMap #-}
toStrictMap (MkSeries ks vs) = MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)
-- | Construct a series from a strict 'Data.Map.Strict.Map'.
fromStrictMap :: (Vector v a) => MS.Map k a -> Series v k a
{-# INLINABLE fromStrictMap #-}
fromStrictMap mp = MkSeries { index = Index.toIndex $ MS.keysSet mp
, values = Vector.fromListN (MS.size mp) $ MS.elems mp
}
instance (Vector v a) => IsSeries (IntMap a) v Int a where
toSeries :: IntMap a -> Series v Int a
toSeries im = MkSeries
{ index = Index.toIndex $ IntMap.keysSet im
, values = Vector.fromListN (IntMap.size im) $ IntMap.elems im
}
{-# INLINABLE toSeries #-}
fromSeries :: Series v Int a -> IntMap a
fromSeries (MkSeries ks vs)
= IntMap.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)
{-# INLINABLE fromSeries #-}
instance (Ord k, Vector v a) => IsSeries (Seq (k, a)) v k a where
toSeries :: Seq (k, a) -> Series v k a
toSeries = toSeries . Foldable.toList
{-# INLINABLE toSeries #-}
fromSeries :: Series v k a -> Seq (k, a)
fromSeries = Seq.fromList . fromSeries
{-# INLINABLE fromSeries #-}
instance (Vector v a) => IsSeries (Set (k, a)) v k a where
toSeries :: Set (k, a) -> Series v k a
toSeries = fromDistinctAscList . Set.toAscList
{-# INLINABLE toSeries #-}
fromSeries :: Series v k a -> Set (k, a)
fromSeries = Set.fromDistinctAscList . toList
{-# INLINABLE fromSeries #-}
-- | Get the first value of a 'Series'. If the 'Series' is empty,
-- this function returns 'Nothing'.
headM :: Vector v a => Series v k a -> Maybe a
{-# INLINABLE headM #-}
headM (MkSeries _ vs) = Vector.headM vs
-- | Get the last value of a 'Series'. If the 'Series' is empty,
-- this function returns 'Nothing'.
lastM :: Vector v a => Series v k a -> Maybe a
{-# INLINABLE lastM #-}
lastM (MkSeries _ vs) = Vector.lastM vs
-- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.
take :: Vector v a => Int -> Series v k a -> Series v k a
{-# INLINABLE take #-}
take n (MkSeries ks vs)
-- Index.take is O(log n) while Vector.take is O(1)
= MkSeries (Index.take n ks) (Vector.take n vs)
-- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.
drop :: Vector v a => Int -> Series v k a -> Series v k a
{-# INLINABLE drop #-}
drop n (MkSeries ks vs)
-- Index.drop is O(log n) while Vector.drop is O(1)
= MkSeries (Index.drop n ks) (Vector.drop n vs)
-- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.
takeWhile :: Vector v a => (a -> Bool) -> Series v k a -> Series v k a
{-# INLINABLE takeWhile #-}
takeWhile f (MkSeries ix vs) = let taken = Vector.takeWhile f vs
in MkSeries { index = Index.take (Vector.length taken) ix
, values = taken
}
-- | \(O(n)\) Returns the complement of 'takeWhile'.
dropWhile :: Vector v a => (a -> Bool) -> Series v k a -> Series v k a
{-# INLINABLE dropWhile #-}
dropWhile f (MkSeries ix vs) = let dropped = Vector.dropWhile f vs
in MkSeries { index = Index.drop (Index.size ix - Vector.length dropped) ix
, values = dropped
}
-- | \(O(n)\) Map every element of a 'Series'.
map :: (Vector v a, Vector v b)
=> (a -> b) -> Series v k a -> Series v k b
{-# INLINABLE map #-}
map f (MkSeries ix xs) = MkSeries ix $ Vector.map f xs
-- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.
mapWithKey :: (Vector v a, Vector v b)
=> (k -> a -> b) -> Series v k a -> Series v k b
{-# INLINABLE mapWithKey #-}
mapWithKey f (MkSeries ix xs)
-- We're using boxed vectors to map because we don't want any restrictions
-- on the index type, i.e. we don't want the constraint Vector v k
= let vs = Boxed.zipWith f (Index.toAscVector ix) (Vector.convert xs)
in MkSeries ix (Vector.convert vs)
-- | \(O(n \log n)\).
-- Map each key in the index to another value. Note that the resulting series
-- may have less elements, because each key must be unique.
--
-- In case new keys are conflicting, the first element is kept.
mapIndex :: (Vector v a, Ord k, Ord g) => Series v k a -> (k -> g) -> Series v g a
{-# INLINABLE mapIndex #-}
mapIndex (MkSeries index values) f
-- Note that the order in which items are kept appears to be backwards;
-- See the examples for Data.Map.Strict.fromListWith
= let mapping = MS.fromListWith (\_ x -> x) $ [(f k, k) | k <- Index.toAscList index]
newvalues = fmap (\k -> values Vector.! Index.Internal.findIndex k index) mapping
in toSeries newvalues
-- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.
concatMap :: (Vector v a, Vector v k, Vector v b, Vector v (k, a), Vector v (k, b), Ord k)
=> (a -> Series v k b)
-> Series v k a
-> Series v k b
{-# INLINABLE concatMap #-}
concatMap f = fromVector
. Vector.concatMap (toVector . f . snd)
. toVector
instance (Vector v a, Ord k) => Semigroup (Series v k a) where
{-# INLINABLE (<>) #-}
(<>) :: Series v k a -> Series v k a -> Series v k a
-- Despite all my effort, merging via conversion to Map remains fastest.
xs <> ys = toSeries $ toStrictMap xs <> toStrictMap ys
{-# INLINABLE sconcat #-}
sconcat = toSeries . sconcat . fmap toStrictMap
instance (Vector v a, Ord k) => Monoid (Series v k a) where
{-# INLINABLE mempty #-}
mempty :: Series v k a
mempty = MkSeries mempty Vector.empty
{-# INLINABLE mappend #-}
mappend :: Series v k a -> Series v k a -> Series v k a
mappend = (<>)
{-# INLINABLE mconcat #-}
mconcat :: [Series v k a] -> Series v k a
mconcat = toSeries . mconcat . fmap toStrictMap
instance (Vector v a, Eq k, Eq a) => Eq (Series v k a) where
{-# INLINABLE (==) #-}
(==) :: Series v k a -> Series v k a -> Bool
(MkSeries ks1 vs1) == (MkSeries ks2 vs2) = (ks1 == ks2) && (vs1 `Vector.eq` vs2)
instance (Vector v a, Ord (v a), Ord k, Ord a) => Ord (Series v k a) where
{-# INLINABLE compare #-}
compare :: Series v k a -> Series v k a -> Ordering
compare (MkSeries ks1 vs1) (MkSeries ks2 vs2) = compare (ks1, vs1) (ks2, vs2)
instance (Functor v) => Functor (Series v k) where
{-# INLINABLE fmap #-}
fmap :: (a -> b) -> Series v k a -> Series v k b
fmap f (MkSeries ks vs) = MkSeries ks (fmap f vs)
instance (forall a. Vector v a, Functor v) => FunctorWithIndex k (Series v k) where
{-# INLINABLE imap #-}
imap :: (k -> a -> b) -> Series v k a -> Series v k b
imap = mapWithKey
-- Inlining all methods in 'Foldable'
-- is important in order for folds over a boxed
-- Series to have performance characteristics
-- be as close as possible to boxed vectors
instance (Foldable v) => Foldable (Series v k) where
{-# INLINABLE fold #-}
fold :: Monoid m => Series v k m -> m
fold = Foldable.fold . values
{-# INLINABLE foldMap #-}
foldMap :: (Monoid m) => (a -> m) -> Series v k a -> m
foldMap f = Foldable.foldMap f . values
{-# INLINABLE foldMap' #-}
foldMap' :: (Monoid m) => (a -> m) -> Series v k a -> m
foldMap' f = Foldable.foldMap f . values
{-# INLINABLE foldr #-}
foldr :: (a -> b -> b) -> b -> Series v k a -> b
foldr f i = Foldable.foldr f i . values
{-# INLINABLE foldr' #-}
foldr' :: (a -> b -> b) -> b -> Series v k a -> b
foldr' f i = Foldable.foldr' f i . values
{-# INLINABLE foldl #-}
foldl :: (b -> a -> b) -> b -> Series v k a -> b
foldl f i = Foldable.foldl f i . values
{-# INLINABLE foldl' #-}
foldl' :: (b -> a -> b) -> b -> Series v k a -> b
foldl' f i = Foldable.foldl' f i . values
{-# INLINABLE foldr1 #-}
foldr1 :: (a -> a -> a) -> Series v k a -> a
foldr1 f = Foldable.foldr1 f . values
{-# INLINABLE foldl1 #-}
foldl1 :: (a -> a -> a) -> Series v k a -> a
foldl1 f = Foldable.foldl1 f . values
{-# INLINABLE toList #-}
toList :: Series v k a -> [a]
toList = Foldable.toList . values
{-# INLINABLE null #-}
null :: Series v k a -> Bool
null = Foldable.null . values
{-# INLINABLE length #-}
length :: Series v k a -> Int
length = Foldable.length . values
{-# INLINABLE elem #-}
elem :: Eq a => a -> Series v k a -> Bool
elem e = Foldable.elem e . values
{-# INLINABLE maximum #-}
maximum :: Ord a => Series v k a -> a
maximum = Foldable.maximum . values
{-# INLINABLE minimum #-}
minimum :: Ord a => Series v k a -> a
minimum = Foldable.minimum . values
{-# INLINABLE sum #-}
sum :: Num a => Series v k a -> a
sum = Foldable.sum . values
{-# INLINABLE product #-}
product :: Num a => Series v k a -> a
product = Foldable.product . values
instance (forall a. Vector v a, Vector v k, Foldable v, Functor v) => FoldableWithIndex k (Series v k) where
{-# INLINABLE ifoldMap #-}
ifoldMap :: Monoid m => (k -> a -> m) -> Series v k a -> m
ifoldMap = foldMapWithKey
instance (Foldable v) => Bifoldable (Series v) where
{-# INLINABLE bifoldMap #-}
bifoldMap :: Monoid m => (k -> m) -> (a -> m) -> Series v k a -> m
bifoldMap fk fv (MkSeries ks vs) = P.foldMap fk ks <> Foldable.foldMap fv vs
instance (Traversable v) => Traversable (Series v k) where
{-# INLINABLE traverse #-}
traverse :: Applicative f
=> (a -> f b) -> Series v k a -> f (Series v k b)
traverse f (MkSeries ix vs) = MkSeries ix <$> traverse f vs
instance (forall a. Vector v a, Functor v, Foldable v, Ord k, Traversable v) => TraversableWithIndex k (Series v k) where
{-# INLINABLE itraverse #-}
itraverse :: Applicative f => (k -> a -> f b) -> Series v k a -> f (Series v k b)
itraverse = traverseWithKey
-- | \(O(n)\) Execute a 'Fold' over a 'Series'.
--
-- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into
-- account while folding.
fold :: Vector v a
=> Fold a b
-> Series v k a
-> b
fold (Fold step init' extract)
= extract . Vector.foldl' step init' . values
{-# INLINABLE fold #-}
-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.
--
-- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into
-- account while folding.
foldM :: (Monad m, Vector v a)
=> FoldM m a b
-> Series v k a
-> m b
foldM (FoldM step init' extract) xs
= init' >>= \i -> Vector.foldM' step i (values xs) >>= extract
{-# INLINABLE foldM #-}
-- | \(O(n)\) Execute a 'Fold' over a 'Series', where the 'Fold' takes keys into account.
foldWithKey :: (Vector v a, Vector v k, Vector v (k, a))
=> Fold (k, a) b
-> Series v k a
-> b
foldWithKey (Fold step init' extract)
= extract . Vector.foldl' step init' . toVector
{-# INLINABLE foldWithKey #-}
-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.
foldMWithKey :: (Monad m, Vector v a, Vector v k, Vector v (k, a))
=> FoldM m (k, a) b
-> Series v k a
-> m b
foldMWithKey (FoldM step init' extract) xs
= init' >>= \i -> Vector.foldM' step i (toVector xs) >>= extract
{-# INLINABLE foldMWithKey #-}
-- | \(O(n)\) Fold over elements in a 'Series'.
foldMap :: (Monoid m, Vector v a) => (a -> m) -> Series v k a -> m
{-# INLINABLE foldMap #-}
foldMap f = Vector.foldMap f . values
-- | \(O(n)\) Fold over pairs of keys and elements in a 'Series'.
-- See also 'bifoldMap'.
foldMapWithKey :: (Monoid m, Vector v a, Vector v k, Vector v (k, a)) => (k -> a -> m) -> Series v k a -> m
{-# INLINABLE foldMapWithKey #-}
foldMapWithKey f = Vector.foldMap (uncurry f) . toVector
-- | \(O(n)\) Fold over keys and elements separately in a 'Series'.
-- See also 'foldMapWithKey'.
bifoldMap :: (Vector v a, Monoid m) => (k -> m) -> (a -> m) -> Series v k a -> m
{-# INLINABLE bifoldMap #-}
bifoldMap fk fv (MkSeries ks vs) = P.foldMap fk ks <> Vector.foldMap fv vs
-- | \(O(1)\) Extract the length of a 'Series'.
length :: Vector v a => Series v k a -> Int
{-# INLINABLE length #-}
length = Vector.length . values
-- | \(O(1)\) Test whether a 'Series' is empty.
null :: Vector v a => Series v k a -> Bool
{-# INLINABLE null #-}
null = Vector.null . values
-- | \(O(n)\) Apply the monadic action to every element of a series and its
-- index, yielding a series of results.
mapWithKeyM :: (Vector v a, Vector v b, Monad m, Ord k)
=> (k -> a -> m b) -> Series v k a -> m (Series v k b)
{-# INLINABLE mapWithKeyM #-}
mapWithKeyM f xs = let f' (key, val) = (key,) <$> f key val
in fmap fromList $ traverse f' $ toList xs
-- | \(O(n)\) Apply the monadic action to every element of a series and its
-- index, discarding the results.
mapWithKeyM_ :: (Vector v a, Monad m)
=> (k -> a -> m b) -> Series v k a -> m ()
{-# INLINABLE mapWithKeyM_ #-}
mapWithKeyM_ f xs = let f' (key, val) = (key,) <$> f key val
in mapM_ f' $ toList xs
-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys,
-- yielding a series of results.
forWithKeyM :: (Vector v a, Vector v b, Monad m, Ord k) => Series v k a -> (k -> a -> m b) -> m (Series v k b)
{-# INLINABLE forWithKeyM #-}
forWithKeyM = flip mapWithKeyM
-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys,
-- discarding the results.
forWithKeyM_ :: (Vector v a, Monad m) => Series v k a -> (k -> a -> m b) -> m ()
{-# INLINABLE forWithKeyM_ #-}
forWithKeyM_ = flip mapWithKeyM_
-- | \(O(n)\) Traverse a 'Series' with an Applicative action, taking into account both keys and values.
traverseWithKey :: (Applicative t, Ord k, Traversable v, Vector v a, Vector v b, Vector v k, Vector v (k, a), Vector v (k, b))
=> (k -> a -> t b)
-> Series v k a
-> t (Series v k b)
{-# INLINABLE traverseWithKey #-}
traverseWithKey f = fmap fromVector
. traverse (\(k, x) -> (k,) <$> f k x)
. toVector
instance (NFData (v a), NFData k) => NFData (Series v k a) where
rnf :: Series v k a -> ()
rnf (MkSeries ks vs) = rnf ks `seq` rnf vs
instance (Vector v a, Ord k, Show k, Show a) => Show (Series v k a) where
show :: Series v k a -> String
show = display
-- | Options controlling how to display 'Series' in the 'displayWith' function.
-- Default options are provided by 'defaultDisplayOptions'.
--
-- To help with creating 'DisplayOptions', see 'noLongerThan'.
data DisplayOptions k a
= DisplayOptions
{ maximumNumberOfRows :: Int
-- ^ Maximum number of rows shown. These rows will be distributed evenly
-- between the start of the 'Series' and the end.
, indexHeader :: String
-- ^ Header of the index column.
, valuesHeader :: String
-- ^ Header of the values column.
, keyDisplayFunction :: k -> String
-- ^ Function used to display keys from the 'Series'. Use 'noLongerThan'
-- to control the width of the index column.
, valueDisplayFunction :: a -> String
-- ^ Function used to display values from the 'Series'. Use 'noLongerThan'
-- to control the width of the values column.
}
-- | Default 'Series' display options.
defaultDisplayOptions :: (Show k, Show a) => DisplayOptions k a
defaultDisplayOptions
= DisplayOptions { maximumNumberOfRows = 6
, indexHeader = "index"
, valuesHeader = "values"
, keyDisplayFunction = show
, valueDisplayFunction = show
}
-- | This function modifies existing functions to limit the width of its result.
--
-- >>> let limit7 = (show :: Int -> String) `noLongerThan` 7
-- >>> limit7 123456789
-- "123456..."
noLongerThan :: (a -> String) -> Int -> (a -> String)
noLongerThan f len x
= let raw = f x
in if List.length raw <= max 0 len
then raw
else List.take (List.length raw - 3) raw <> "..."
-- | Display a 'Series' using default 'DisplayOptions'.
display :: (Vector v a, Show k, Show a)
=> Series v k a
-> String
display = displayWith defaultDisplayOptions
-- | Display a 'Series' using customizable 'DisplayOptions'.
displayWith :: (Vector v a)
=> DisplayOptions k a
-> Series v k a
-> String
displayWith DisplayOptions{..} xs
= formatGrid $ if length xs > max 0 maximumNumberOfRows
then let headlength = max 0 maximumNumberOfRows `div` 2
taillength = max 0 maximumNumberOfRows - headlength
in mconcat [ [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList $ take headlength xs]
, [ ("...", "...") ]
, [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList $ drop (length xs - taillength) xs]
]
else [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList xs ]
where
-- | Format a grid represented by a list of rows, where every row is a list of items
-- All columns will have a fixed width
formatGrid :: [ (String, String) ] -- List of rows
-> String
formatGrid rows = mconcat $ List.intersperse "\n"
$ [ pad indexWidth k <> " | " <> pad valuesWidth v
| (k, v) <- rows'
]
where
rows' = [ (indexHeader, valuesHeader) ] <> [ ("-----", "------")] <> rows
(indexCol, valuesCol) = unzip rows'
width col = maximum (P.length <$> col)
indexWidth = width indexCol
valuesWidth = width valuesCol
-- | Pad a string to a minimum of @n@ characters wide.
pad :: Int -> String -> String
pad n s
| n <= P.length s = s
| otherwise = replicate (n - P.length s) ' ' <> s