assoc-listlike-0.1.0.0: src/Data/AssocList/ListLike/Predicate.hs
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, ViewPatterns #-}
-- | Functions on association lists that involve 'Predicate's on the keys.
module Data.AssocList.ListLike.Predicate
(
-- * Related modules
-- $relatedModules
-- * Lookup
lookupFirst
, lookupAll
-- * Removal
, removeFirst
, removeAll
-- * Mapping
-- $mapping
, mapFirst
, mapAll
-- * Grouping
, partition
, break
, breakPartition
) where
import Data.AssocList.ListLike.Concept
-- base
import Control.Exception (throw)
import Prelude (Eq (..), Maybe (..), maybe, error, otherwise, (<$>))
-- ListLike
import Data.ListLike (cons, uncons)
import qualified Data.ListLike as LL
-- contravariant
import Data.Functor.Contravariant (Predicate (..))
-- $setup
-- >>> import Prelude ((==), negate)
-- $relatedModules
-- Some other modules that are a lot like this one:
--
-- * "Data.AssocList.ListLike.Eq" - Functions on association lists that
-- make use of an 'Eq' constraint on the type of the keys
-- * "Data.AssocList.ListLike.Equivalence" - Functions on association
-- lists that involve 'Equivalence's on the keys
-- | Obtain the first value associated with a key that satisfies a
-- predicate, if such a mapping is present.
--
-- >>> lookupFirst (Predicate (== 'B')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- Just 2
--
-- The result is 'Nothing' if no key in the list satisfies the predicate.
--
-- >>> lookupFirst (Predicate (== 'D')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- Nothing
lookupFirst :: forall l a b. AssocList l a b
=> Predicate a -> l -> Maybe b
lookupFirst _key (uncons -> Nothing) = Nothing
lookupFirst key (uncons -> Just ((x, y), xys))
| getPredicate key x = Just y
| otherwise = lookupFirst key xys
-- | Obtain all values associated with keys that satisfy the predicate,
-- in the order in which the mappings appear in the list.
--
-- >>> lookupAll (Predicate (== 'B')) [('A',1), ('B',2), ('B',3), ('C',4), ('B',3)]
-- [2,3,3]
lookupAll :: forall l a b. AssocList l a b
=> Predicate a -> l -> [b]
lookupAll _key (uncons -> Nothing) = []
lookupAll key (uncons -> Just ((x, y), xys))
| getPredicate key x = y : lookupAll key xys
| otherwise = lookupAll key xys
-- | Produce a modified version of the association list in which the
-- first occurrence of a key that satisfied the predicate has been removed.
--
-- >>> removeFirst (Predicate (== 'B')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- [('A',1),('B',3),('C',4)]
--
-- If no key in the list satisfies the predicate, then the original list
-- is returned.
--
-- >>> removeFirst (Predicate (== 'C')) [('A',1), ('B',2), ('B',3)]
-- [('A',1),('B',2),('B',3)]
removeFirst :: forall l a b. AssocList l a b
=> Predicate a -> l -> l
removeFirst _key l@(uncons -> Nothing) = l
removeFirst key (uncons -> Just (xy@(x, y), xys))
| getPredicate key x = xys
| otherwise = cons xy (removeFirst key xys)
-- | Produce a modified version of the association list in which all
-- occurrences of keys that satisfy the predicate have been removed.
--
-- >>> removeAll (Predicate (== 'B')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- [('A',1),('C',4)]
--
-- If the key is not present in the mapping, then the original list
-- is returned.
--
-- >>> removeAll (Predicate (== 'C')) [('A',1), ('B',2), ('B',3)]
-- [('A',1),('B',2),('B',3)]
removeAll :: forall l a b. AssocList l a b
=> Predicate a -> l -> l
removeAll _key l@(uncons -> Nothing) = l
removeAll key (uncons -> Just (xy@(x, y), xys))
| getPredicate key x = removeAll key xys
| otherwise = cons xy (removeAll key xys)
-- | Produces a tuple of two results:
--
-- 1. All values associated with keys that satify the predicate
-- 2. All of the other key-value pairs
--
-- @'partition' x l = ('lookupAll' x l, 'removeAll' x l)@
--
-- >>> partition (Predicate (== 'B')) [('A',1), ('B',2), ('B',3), ('C',4), ('B',3)]
-- ([2,3,3],[('A',1),('C',4)])
partition :: forall l a b. AssocList l a b
=> Predicate a -> l -> ([b], l)
partition _key l@(uncons -> Nothing) = ([], l)
partition key (uncons -> Just (xy@(x, y), xys))
| getPredicate key x = (y : yes , no)
| otherwise = ( yes , cons xy no)
where
(yes, no) = partition key xys
-- | Produces a tuple of two results:
--
-- 1. The longest prefix of the association list that does /not/ contain
-- a key satisfying the predict
-- 2. The remainder of the list
--
-- >>> break (Predicate (== 'B')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- ([('A',1)],[('B',2),('B',3),('C',4)])
--
-- If the key of the first mapping in the list satisfies the predicate,
-- then the first part of the resulting tuple is empty, and the second
-- part of the result is the entire list.
--
-- >>> break (Predicate (== 'A')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- ([],[('A',1),('B',2),('B',3),('C',4)])
--
-- If no key in the list satisfies the predicate, then the first part of
-- the resulting tuple is the entire list, and the second part of the
-- result is empty.
--
-- >>> break (Predicate (== 'D')) [('A',1), ('B',2), ('B',3), ('C',4)]
-- ([('A',1),('B',2),('B',3),('C',4)],[])
break :: forall l a b. AssocList l a b
=> Predicate a -> l -> (l, l)
break key = LL.break (\(x, y) -> getPredicate key x)
-- | 'break' on a predicate, then 'partition' the remainder.
--
-- @'breakPartition' p l@ separates @l@ into three parts:
--
-- 1. The key-value pairs for which the predicate is /not/ satisfied that
-- occur in the list /before/ the first occurrence of a key that satisfies
-- the predicate (@fst ('break' p l)@)
-- 2. All values associated with keys that satisfy the predicate
-- (@'lookupAll' p l@)
-- 3. The key-value pairs for which the predicate is /not/ satisfied that
-- occur in the list /after/ the first occurrence of a key that satisfies
-- the predicate (@'removeAll' p (snd ('break' p l))@)
--
-- >>> breakPartition (Predicate (== 'B')) [('A',1),('B',2),('C',3),('B',4)]
-- ([('A',1)],[2,4],[('C',3)])
--
-- If the predicate is not satisfied by any key in the list, then the
-- first part of the result is the entire list, and the other parts are
-- empty.
--
-- >>> breakPartition (Predicate (== 'D')) [('A',1),('B',2),('C',3),('B',4)]
-- ([('A',1),('B',2),('C',3),('B',4)],[],[])
breakPartition :: forall l a b. AssocList l a b
=> Predicate a -> l -> (l, [b], l)
breakPartition key l =
let
(before, l') = break key l
(xs, after) = partition key l'
in
(before, xs, after)
-- $mapping
-- The "map" functions modify values while preserving the structure of
-- the assocative list. The resulting list has the same size and order
-- as the original.
-- | At the position where a key satisfying the predicate first appears
-- in the list, apply a function to the corresponding value.
--
-- >>> mapFirst (Predicate (== 'B')) negate [('A', 1), ('B', 4), ('C', 2), ('B', 6)]
-- [('A',1),('B',-4),('C',2),('B',6)]
--
-- If no key in the list satisfies the predicate, then the original list is
-- returned without modification.
--
-- >>> mapFirst (Predicate (== 'D')) negate [('A', 1), ('B', 4), ('C', 2), ('B', 6)]
-- [('A',1),('B',4),('C',2),('B',6)]
mapFirst :: forall l a b. AssocList l a b
=> Predicate a -> (b -> b) -> l -> l
mapFirst key f l =
let
(before, l') = break key l
in
before `LL.append`
case (uncons l') of
Nothing -> l'
Just ((x, y), after) -> cons (x, f y) after
-- | At each position in the list where the key satisfies the predicate,
-- apply a function to the corresponding value.
--
-- >>> mapAll (Predicate (== 'B')) negate [('A', 1), ('B', 4), ('C', 2), ('B', 6)]
-- [('A',1),('B',-4),('C',2),('B',-6)]
--
-- If no key in the list satisfies the predicate, then the original list is
-- returned without modification.
--
-- >>> mapAll (Predicate (== 'D')) negate [('A', 1), ('B', 4), ('C', 2), ('B', 6)]
-- [('A',1),('B',4),('C',2),('B',6)]
mapAll :: forall l a b. AssocList l a b
=> Predicate a -> (b -> b) -> l -> l
mapAll key f =
LL.map g
where
g xy@(x, y)
| getPredicate key x = (x, f y)
| otherwise = xy