vector-split-1.0.0.2: src/Data/Vector/Split.hs
module Data.Vector.Split
( chunksOf
, splitPlaces
, splitPlacesBlanks
, chop
, divvy
, module Data.Vector.Split.Internal
) where
import Data.Vector.Generic (Vector)
import qualified Data.Vector.Generic as V
import Data.List (unfoldr)
import Data.Vector.Split.Internal
-- | @'chunksOf' n@ splits a vector into length-n pieces. The last
-- piece will be shorter if @n@ does not evenly divide the length of
-- the vector. If @n <= 0@, @'chunksOf' n l@ returns an infinite list
-- of empty vectors. For example:
--
-- Note that @'chunksOf' n []@ is @[]@, not @[[]]@. This is
-- intentional, and is consistent with a recursive definition of
-- 'chunksOf'; it satisfies the property that
--
-- @chunksOf n xs ++ chunksOf n ys == chunksOf n (xs ++ ys)@
--
-- whenever @n@ evenly divides the length of @xs@.
chunksOf :: Vector v a => Int -> v a -> [v a]
chunksOf i = unfoldr go
where go v | V.null v = Nothing
| otherwise = Just (V.splitAt i v)
-- | Split a vector into chunks of the given lengths. For example:
--
-- > splitPlaces [2,3,4] [1..20] == [[1,2],[3,4,5],[6,7,8,9]]
-- > splitPlaces [4,9] [1..10] == [[1,2,3,4],[5,6,7,8,9,10]]
-- > splitPlaces [4,9,3] [1..10] == [[1,2,3,4],[5,6,7,8,9,10]]
--
-- If the input vector is longer than the total of the given lengths,
-- then the remaining elements are dropped. If the vector is shorter
-- than the total of the given lengths, then the result may contain
-- fewer chunks than requested, and the last chunk may be shorter
-- than requested.
splitPlaces :: Vector v a => [Int] -> v a -> [v a]
splitPlaces is v = unfoldr go (is,v)
where go ([],_) = Nothing
go (x:xs,y) | V.null y = Nothing
| otherwise = let (l,r) = V.splitAt x y in Just (l,(xs,r))
-- | Split a vector into chunks of the given lengths. Unlike
-- 'splitPlaces', the output list will always be the same length as
-- the first input argument. If the input vector is longer than the
-- total of the given lengths, then the remaining elements are
-- dropped. If the vector is shorter than the total of the given
-- lengths, then the last several chunks will be shorter than
-- requested or empty. For example:
--
-- > splitPlacesBlanks [2,3,4] [1..20] == [[1,2],[3,4,5],[6,7,8,9]]
-- > splitPlacesBlanks [4,9] [1..10] == [[1,2,3,4],[5,6,7,8,9,10]]
-- > splitPlacesBlanks [4,9,3] [1..10] == [[1,2,3,4],[5,6,7,8,9,10],[]]
--
-- Notice the empty list in the output of the third example, which
-- differs from the behavior of 'splitPlaces'.
splitPlacesBlanks :: Vector v a => [Int] -> v a -> [v a]
splitPlacesBlanks is v = unfoldr go (is,v)
where go ([],_) = Nothing
go (x:xs,y) = let (l,r) = V.splitAt x y in Just (l,(xs,r))
-- | A useful recursion pattern for processing a list to produce a new
-- list, often used for \"chopping\" up the input list. Typically
-- chop is called with some function that will consume an initial
-- prefix of the list and produce a value and the rest of the list.
--
-- For example, many common Prelude functions can be implemented in
-- terms of @chop@:
--
-- > group :: (Eq a) => [a] -> [[a]]
-- > group = chop (\ xs@(x:_) -> span (==x) xs)
-- >
-- > words :: String -> [String]
-- > words = filter (not . null) . chop (span (not . isSpace) . dropWhile isSpace)
chop :: Vector v a => (v a -> (b, v a)) -> v a -> [b]
chop f v | V.null v = []
| otherwise = b : chop f v'
where (b, v') = f v
-- | Divides up an input vector into a set of subvectors, according to 'n' and 'm'
-- input specifications you provide. Each subvector will have 'n' items, and the
-- start of each subvector will be offset by 'm' items from the previous one.
--
-- > divvy 5 5 [1..20] == [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15],[16,17,18,19,20]]
--
-- In the case where a source vector's trailing elements do no fill an entire
-- subvector, those trailing elements will be dropped.
--
-- > divvy 5 2 [1..10] == [[1,2,3,4,5],[3,4,5,6,7],[5,6,7,8,9]]
--
-- As an example, you can generate a moving average over a vector of prices:
--
-- > type Prices = [Float]
-- > type AveragePrices = [Float]
-- >
-- > average :: [Float] -> Float
-- > average xs = sum xs / (fromIntegral $ length xs)
-- >
-- > simpleMovingAverage :: Prices -> AveragePrices
-- > simpleMovingAverage priceList =
-- > map average divvyedPrices
-- > where divvyedPrices = divvy 20 1 priceList
divvy :: Vector v a => Int -> Int -> v a -> [v a]
divvy n m v | V.null v = []
| otherwise = filter (\ws -> n == V.length ws)
. chop (\xs -> (V.take n xs, V.drop m xs))
$ v