{-| Module : Data.List.Filter
Description : Special takes and drops on lists
Copyright : (c) Preetham Gujjula, 2020
License : BSD3
Maintainer : pgujjula+list-utilities@protonmail.com
Stability : experimental
Special takes and drops on lists.
-}
module Data.List.Filter (
takeEvery
, dropEvery
, takeUntil
, dropUntil
) where
{-| @takeEvery n xs@ is a list of every @n@th element of @xs@.
__Precondition:__ @n@ must be positive.
>>> takeEvery 3 [1..10]
[3, 6, 9]
>>> takeEvery 1 [1..10] == [1..10]
True
-}
takeEvery :: Int -> [a] -> [a]
takeEvery step xs = compute validated
where
compute ys = case drop (step - 1) ys of
[] -> []
y:ys' -> y : compute ys'
validated
| step > 0 = xs
| otherwise = error $ "Data.List.Transform.takeEvery: Step parameter "
++ "must be positive."
{-| @dropEvery n xs@ is a list of every @n@th element of @xs@.
__Precondition:__ @n@ must be positive.
>>> dropEvery 3 [1..10]
[1, 2, 4, 5, 7, 8, 10]
>>> dropEvery 1 [1..10]
[]
-}
dropEvery :: Int -> [a] -> [a]
dropEvery step xs = compute validated
where
compute ys = case splitAt (step - 1) ys of
(as, []) -> as
(as, _:bs) -> as ++ compute bs
validated
| step > 0 = xs
| otherwise = error $ "Data.List.Transform.dropEvery: Step parameter "
++ "must be positive."
{-| Take a list until a predicate is satisfied, and include the element
satisfying the predicate.
>>> takeUntil (== 5) [1..]
[1, 2, 3, 4, 5]
>>> takeUntil (== 7) [3, 2, 1]
[3, 2, 1]
>>> takeUntil undefined []
[]
Note that @takeUntil@ on a nonempty list must always yield the first
element, and the implementation is lazy enough to take advantage of this
fact.
>>> head (takeUntil undefined [1..])
1
-}
takeUntil :: (a -> Bool) -> [a] -> [a]
takeUntil _ [] = []
takeUntil _ [x] = [x]
takeUntil f (x:xs) = x : (if f x then [] else takeUntil f xs)
{-| Drop a list until a predicate is satisfied, and don't include the element
satisfying the predicate.
>>> dropUntil (== 5) [1..10]
[6, 7, 8, 9, 10]
>>> dropUntil (< 0) [1, 2, 3]
[]
>>> dropUntil undefined []
[]
Note that @dropUntil@ on a nonempty list must always drop the first
element, and the implementation is lazy enough to take advantage of this
fact.
>>> dropUntil undefined [undefined]
[]
-}
dropUntil :: (a -> Bool) -> [a] -> [a]
dropUntil _ [] = []
dropUntil _ [_] = []
dropUntil f (x:xs) = if f x then xs else dropUntil f xs