toolshed-0.11.1.0: src/ToolShed/ListPlus.hs
{-
Copyright (C) 2010 Dr. Alistair Ward
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
{- |
[@AUTHOR@] Dr. Alistair Ward
[@DESCRIPTION@] Miscellaneous polymorphic list-operations.
-}
module ToolShed.ListPlus(
-- * Types
-- ** Type-synonyms
ChunkLength,
-- Split,
-- * Functions
chunk,
excise,
groupComparing,
merge,
mergeBy,
-- splitsFrom,
splitsLeftFrom,
splitsRightFrom,
takeUntil
) where
import qualified Control.Arrow
import qualified Data.List
-- | The length of the chunks into which a list is split.
type ChunkLength = Int
{- |
* Splits a list into length-@size@ pieces, where @(size >= 0)@.
* The last chunk will be shorter, if @n@ isn't an aliquot part of the input list-length.
* If @(size == 0)@, the resulting list will be an infinite sequence of null lists.
* CAVEAT: a similar function is available in the module /Data.List.Split/, though this one checks for @(size < 0)@.
-}
chunk ::
ChunkLength
-> [a] -- ^ The polymorphic input list to be chunked.
-> [[a]]
chunk size list
| size < 0 = error $ "ToolShed.ListPlus.chunk:\tnegative chunk-size=" ++ show size
| otherwise = chunk' list
where
chunk' :: [a] -> [[a]]
chunk' [] = []
chunk' a = uncurry (:) . Control.Arrow.second chunk' $ splitAt size a
-- | Remove the single indexed element from the list.
excise ::
Int -- ^ The index.
-> [a] -- ^ The polymorphic input list.
-> [a] -- ^ The same list, with the indexed element removed.
excise 0 = tail --Just for efficiency.
excise i = uncurry (++) . Control.Arrow.second tail . splitAt i
{- |
* Much like 'Data.List.GroupBy', but where the normal binary predicate,
is composed from equality, after the same unary translation-function has been applied to both list-elements.
* cf. 'GHC.Exts.groupWith', which uses the function parameter to both sort and group.
-}
groupComparing :: Eq b
=> (a -> b) -- ^ Translates elements from the list, prior to testing the translated values for equality.
-> [a] -- ^ The polymorphic input list to group.
-> [[a]] -- ^ The same list split into chunks of the required length.
groupComparing f = Data.List.groupBy (\a b -> f a == f b)
{- |
* Merge two sorted lists, to product a single sorted list.
* The merge-process is /stable/, in that where items from each list are equal, they remain in the original order.
-}
merge :: Ord a => [a] -> [a] -> [a]
merge [] r = r
merge l [] = l
merge l@(x : xs) r@(y : ys)
| x > y = y : merge l ys
| otherwise = x : merge xs r
{- |
* Merge two sorted lists, according to the specified order, to product a single sorted list.
* The merge-process is /stable/, in that where items from each list are equal, they remain in the original order.
-}
mergeBy :: Ord a => (a -> a -> Ordering) -> [a] -> [a] -> [a]
mergeBy _ [] r = r
mergeBy _ l [] = l
mergeBy cmp l@(x : xs) r@(y : ys)
| x `cmp` y == GT = y : mergeBy cmp l ys
| otherwise = x : mergeBy cmp xs r
-- | The polymorphic pair, resulting from splitting a list of arbitrary type.
type Split a = ([a] {-left list-}, [a] {-right list-})
-- | Use the specified transformation, to generate a list of 'Split's, from the initial one.
splitsFrom ::
(Split a -> Split a) -- ^ The function used to transform one /split/ into the next.
-> Int -- ^ Index.
-> [a] -- ^ The polymorphic input list from which the /splits/ are generated.
-> [Split a] -- ^ The list of all required splits of the single input list.
splitsFrom transformation i = iterate transformation . splitAt i
{- |
* Create the set of all 'Split's, migrating left from the specified location.
* CAVEAT: 'init' fails when 'fst' has been reduced to null.
-}
splitsLeftFrom ::
Int -- ^ Index.
-> [a] -- ^ The polymorphic input list from which the /splits/ are generated, as the index is stepped left
-> [Split a] -- ^ The list of all required splits of the single input list.
splitsLeftFrom start
| start < 0 = error $ "ToolShed.ListPlus.splitsLeftFrom:\tnegative starting-index; " ++ show start
| otherwise = splitsFrom (\(l, r) -> (init l, last l : r)) start
{- |
* Create the set of all 'Split's, migrating right from the specified location.
* CAVEAT: pattern-match against @ : @ fails, when 'snd' has been reduced to null.
-}
splitsRightFrom ::
Int -- ^ Index.
-> [a] -- ^ The polymorphic input list from which the /splits/ are generated, as the index is stepped right.
-> [Split a] -- ^ The list of all required splits of the single input list.
splitsRightFrom start
| start < 0 = error $ "ToolShed.ListPlus.splitsRightFrom:\tnegative starting-index; " ++ show start
| otherwise = splitsFrom (\(l, r : rs) -> (l ++ [r], rs)) start
{- |
* Take until the specified predicate is satisfied; /including/ the item which satisfied it.
* NB: @takeWhile (not . test)@ would return one fewer item.
-}
takeUntil ::
(a -> Bool) -- ^ Predicate, used to determine the last item taken.
-> [a] -- ^ The polymorphic input list.
-> [a]
takeUntil predicate = takeUntil' where
takeUntil' (x : xs) = x {-take regardless-} : if predicate x
then []
else takeUntil' xs
takeUntil' _ = []