quiver-groups-0.1.0.0: src/Control/Quiver/Group.hs
{-# LANGUAGE RankNTypes #-}
{- |
Module : Control.Quiver.Group
Description : Group and chunk values within a Quiver
Copyright : (c) Ivan Lazar Miljenovic
License : MIT
Maintainer : Ivan.Miljenovic@gmail.com
-}
module Control.Quiver.Group where
import Control.Quiver.SP
import Control.Arrow (second)
import qualified Data.DList as D
--------------------------------------------------------------------------------
-- | Accumulate values within a Quiver.
spaccum :: (a -> p)
-- ^ Create the initial partial accumulation @p@.
-> (p -> a -> Either p (g, Maybe a))
-- ^ Attempt to add a new value to a partial
-- accumulation; returns either an updated partial
-- accumulation or else a completed accumulation @g@ as
-- well as optionally the initial value (if it was /not/
-- added to the completed accumulation).
-> (p -> Maybe g)
-- ^ Attempt to convert the final partial accumulation
-- to a complete accumulation. If this function returns
-- @'Nothing'@ then the final partial accumulation is
-- returned using 'spfailed'.
-> SP a g f p
spaccum mkInit addA finalise = createNewAccum
where
createNewAccum = spconsume newAccumFrom spcomplete
newAccumFrom = accumPartial . mkInit
accumPartial p = spconsume (withValue . addA p) (finalisePartial p)
withValue epa = case epa of
Left p -> accumPartial p
Right (g,ma') -> produce g
(const (maybe createNewAccum newAccumFrom ma'))
spincomplete
finalisePartial p = maybe (spfailed p) spemit (finalise p)
-- | As with 'spaccum' but the finalisation function always succeeds
spaccum' :: (Functor f) => (a -> p) -> (p -> a -> Either p (g, Maybe a)) -> (p -> g) -> SP a g f ()
spaccum' mkInit addA finalise = spaccum mkInit addA (Just . finalise) >&> fmap (fmap (const ()))
{-# ANN spaccum' "HLint: ignore Use void" #-}
-- Don't want to use 'void' to make sure the 'SPResult' is maintained.
--------------------------------------------------------------------------------
-- | Collect consecutive equal elements together.
spgroup :: (Eq a, Functor f) => SP a [a] f ()
spgroup = spgroupBy (==)
-- | Collect consecutive elements together that satisfy the provided
-- function.
spgroupBy :: (Functor f) => (a -> a -> Bool) -> SP a [a] f ()
spgroupBy f = spaccum' mkInit addA finalise
where
mkInit a = (a, D.singleton a)
addA p@(a, d) a'
| f a a' = Left (second (`D.snoc` a') p)
| otherwise = Right (D.toList d, Just a')
finalise = D.toList . snd
--------------------------------------------------------------------------------
-- | Collect the elements into lists of the specified size (though the
-- last such may be shorter).
--
-- A size that is @<= 0@ will return 'spcomplete' (that is, no outputs
-- will be produced).
spchunks :: (Functor f) => Int -> SP a [a] f ()
spchunks n
| n <= 0 = spcomplete
| n == 1 = sppure (:[]) -- Required for the INVARIANT below to be correct
| otherwise = spaccum' mkInit addA finalise
where
mkInit a = (n', D.singleton a)
n' = pred n -- n' is >= 1
-- INVARIANT: c >= 1
addA (c,d) a
| c' <= 0 = Right (D.toList d', Nothing)
| otherwise = Left (c', d')
where
c' = pred c
d' = d `D.snoc` a
finalise = D.toList . snd