2019-12-01 01:55:43 +01:00
|
|
|
{-# LANGUAGE BangPatterns #-}
|
|
|
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
2019-09-28 19:20:57 +02:00
|
|
|
{-# LANGUAGE QuantifiedConstraints #-}
|
2019-12-01 01:55:43 +01:00
|
|
|
--------------------------------------------------------------------------------
|
2019-08-31 19:17:27 +02:00
|
|
|
module Xanthous.Util
|
|
|
|
( EqEqProp(..)
|
|
|
|
, EqProp(..)
|
2019-09-07 20:49:59 +02:00
|
|
|
, foldlMapM
|
|
|
|
, foldlMapM'
|
|
|
|
, between
|
2019-09-15 19:00:28 +02:00
|
|
|
|
|
|
|
, appendVia
|
|
|
|
|
|
|
|
-- * Foldable
|
|
|
|
-- ** Uniqueness
|
|
|
|
-- *** Predicates on uniqueness
|
|
|
|
, isUniqueOf
|
|
|
|
, isUnique
|
|
|
|
-- *** Removing all duplicate elements in n * log n time
|
|
|
|
, uniqueOf
|
|
|
|
, unique
|
|
|
|
-- *** Removing sequentially duplicate elements in linear time
|
|
|
|
, uniqOf
|
|
|
|
, uniq
|
|
|
|
-- ** Bag sequence algorithms
|
|
|
|
, takeWhileInclusive
|
2019-10-06 18:50:29 +02:00
|
|
|
, smallestNotIn
|
2019-12-23 05:22:25 +01:00
|
|
|
, removeVectorIndex
|
2019-12-25 01:40:52 +01:00
|
|
|
, maximum1
|
|
|
|
, minimum1
|
2019-08-31 19:17:27 +02:00
|
|
|
|
2019-12-30 17:31:56 +01:00
|
|
|
-- * Combinators
|
|
|
|
, times, times_
|
|
|
|
|
2019-12-01 01:55:43 +01:00
|
|
|
-- * Type-level programming utils
|
|
|
|
, KnownBool(..)
|
|
|
|
) where
|
|
|
|
--------------------------------------------------------------------------------
|
2019-12-23 05:22:25 +01:00
|
|
|
import Xanthous.Prelude hiding (foldr)
|
2019-12-01 01:55:43 +01:00
|
|
|
--------------------------------------------------------------------------------
|
2019-12-23 05:22:25 +01:00
|
|
|
import Test.QuickCheck.Checkers
|
|
|
|
import Data.Foldable (foldr)
|
|
|
|
import Data.Monoid
|
|
|
|
import Data.Proxy
|
|
|
|
import qualified Data.Vector as V
|
2019-12-25 01:40:52 +01:00
|
|
|
import Data.Semigroup (Max(..), Min(..))
|
|
|
|
import Data.Semigroup.Foldable
|
2019-12-01 01:55:43 +01:00
|
|
|
--------------------------------------------------------------------------------
|
2019-08-31 19:17:27 +02:00
|
|
|
|
|
|
|
newtype EqEqProp a = EqEqProp a
|
|
|
|
deriving newtype Eq
|
|
|
|
|
|
|
|
instance Eq a => EqProp (EqEqProp a) where
|
|
|
|
(=-=) = eq
|
2019-09-07 20:49:59 +02:00
|
|
|
|
|
|
|
foldlMapM :: forall g b a m. (Foldable g, Monoid b, Applicative m) => (a -> m b) -> g a -> m b
|
|
|
|
foldlMapM f = foldr f' (pure mempty)
|
|
|
|
where
|
|
|
|
f' :: a -> m b -> m b
|
|
|
|
f' x = liftA2 mappend (f x)
|
|
|
|
|
|
|
|
-- Strict in the monoidal accumulator. For monads strict
|
|
|
|
-- in the left argument of bind, this will run in constant
|
|
|
|
-- space.
|
|
|
|
foldlMapM' :: forall g b a m. (Foldable g, Monoid b, Monad m) => (a -> m b) -> g a -> m b
|
|
|
|
foldlMapM' f xs = foldr f' pure xs mempty
|
|
|
|
where
|
|
|
|
f' :: a -> (b -> m b) -> b -> m b
|
|
|
|
f' x k bl = do
|
|
|
|
br <- f x
|
|
|
|
let !b = mappend bl br
|
|
|
|
k b
|
|
|
|
|
|
|
|
between
|
|
|
|
:: Ord a
|
|
|
|
=> a -- ^ lower bound
|
|
|
|
-> a -- ^ upper bound
|
|
|
|
-> a -- ^ scrutinee
|
|
|
|
-> Bool
|
|
|
|
between lower upper x = x >= lower && x <= upper
|
2019-09-15 19:00:28 +02:00
|
|
|
|
|
|
|
-- |
|
|
|
|
-- >>> appendVia Sum 1 2
|
|
|
|
-- 3
|
|
|
|
appendVia :: (Rewrapping s t, Semigroup s) => (Unwrapped s -> s) -> Unwrapped s -> Unwrapped s -> Unwrapped s
|
|
|
|
appendVia wrap x y = op wrap $ wrap x <> wrap y
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | Returns True if the targets of the given 'Fold' are unique per the 'Ord' instance for @a@
|
|
|
|
--
|
|
|
|
-- >>> isUniqueOf (folded . _1) ([(1, 2), (2, 2), (3, 2)] :: [(Int, Int)])
|
|
|
|
-- True
|
|
|
|
--
|
|
|
|
-- >>> isUniqueOf (folded . _2) ([(1, 2), (2, 2), (3, 2)] :: [(Int, Int)])
|
|
|
|
-- False
|
|
|
|
--
|
|
|
|
-- @
|
|
|
|
-- 'isUniqueOf' :: Ord a => 'Getter' s a -> s -> 'Bool'
|
|
|
|
-- 'isUniqueOf' :: Ord a => 'Fold' s a -> s -> 'Bool'
|
|
|
|
-- 'isUniqueOf' :: Ord a => 'Lens'' s a -> s -> 'Bool'
|
|
|
|
-- 'isUniqueOf' :: Ord a => 'Iso'' s a -> s -> 'Bool'
|
|
|
|
-- 'isUniqueOf' :: Ord a => 'Traversal'' s a -> s -> 'Bool'
|
|
|
|
-- 'isUniqueOf' :: Ord a => 'Prism'' s a -> s -> 'Bool'
|
|
|
|
-- @
|
|
|
|
isUniqueOf :: Ord a => Getting (Endo (Set a, Bool)) s a -> s -> Bool
|
|
|
|
isUniqueOf aFold = orOf _2 . foldrOf aFold rejectUnique (mempty, True)
|
|
|
|
where
|
|
|
|
rejectUnique x (seen, acc)
|
|
|
|
| seen ^. contains x = (seen, False)
|
|
|
|
| otherwise = (seen & contains x .~ True, acc)
|
|
|
|
|
|
|
|
-- | Returns true if the given 'Foldable' container contains only unique
|
|
|
|
-- elements, as determined by the 'Ord' instance for @a@
|
|
|
|
--
|
|
|
|
-- >>> isUnique ([3, 1, 2] :: [Int])
|
|
|
|
-- True
|
|
|
|
--
|
|
|
|
-- >>> isUnique ([1, 1, 2, 2, 3, 1] :: [Int])
|
|
|
|
-- False
|
|
|
|
isUnique :: (Foldable f, Ord a) => f a -> Bool
|
|
|
|
isUnique = isUniqueOf folded
|
|
|
|
|
|
|
|
|
|
|
|
-- | O(n * log n). Returns a monoidal, 'Cons'able container (a list, a Set,
|
|
|
|
-- etc.) consisting of the unique (per the 'Ord' instance for @a@) targets of
|
|
|
|
-- the given 'Fold'
|
|
|
|
--
|
|
|
|
-- >>> uniqueOf (folded . _2) ([(1, 2), (2, 2), (3, 2), (4, 3)] :: [(Int, Int)]) :: [Int]
|
|
|
|
-- [2,3]
|
|
|
|
--
|
|
|
|
-- @
|
|
|
|
-- 'uniqueOf' :: Ord a => 'Getter' s a -> s -> [a]
|
|
|
|
-- 'uniqueOf' :: Ord a => 'Fold' s a -> s -> [a]
|
|
|
|
-- 'uniqueOf' :: Ord a => 'Lens'' s a -> s -> [a]
|
|
|
|
-- 'uniqueOf' :: Ord a => 'Iso'' s a -> s -> [a]
|
|
|
|
-- 'uniqueOf' :: Ord a => 'Traversal'' s a -> s -> [a]
|
|
|
|
-- 'uniqueOf' :: Ord a => 'Prism'' s a -> s -> [a]
|
|
|
|
-- @
|
|
|
|
uniqueOf
|
|
|
|
:: (Monoid c, Ord w, Cons c c w w) => Getting (Endo (Set w, c)) a w -> a -> c
|
|
|
|
uniqueOf aFold = snd . foldrOf aFold rejectUnique (mempty, mempty)
|
|
|
|
where
|
|
|
|
rejectUnique x (seen, acc)
|
|
|
|
| seen ^. contains x = (seen, acc)
|
|
|
|
| otherwise = (seen & contains x .~ True, cons x acc)
|
|
|
|
|
|
|
|
-- | Returns a monoidal, 'Cons'able container (a list, a Set, etc.) consisting
|
|
|
|
-- of the unique (per the 'Ord' instance for @a@) contents of the given
|
|
|
|
-- 'Foldable' container
|
|
|
|
--
|
|
|
|
-- >>> unique [1, 1, 2, 2, 3, 1] :: [Int]
|
|
|
|
-- [2,3,1]
|
|
|
|
|
|
|
|
-- >>> unique [1, 1, 2, 2, 3, 1] :: Set Int
|
|
|
|
-- fromList [3,2,1]
|
|
|
|
unique :: (Foldable f, Cons c c a a, Ord a, Monoid c) => f a -> c
|
|
|
|
unique = uniqueOf folded
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | O(n). Returns a monoidal, 'Cons'able container (a list, a Vector, etc.)
|
|
|
|
-- consisting of the targets of the given 'Fold' with sequential duplicate
|
|
|
|
-- elements removed
|
|
|
|
--
|
|
|
|
-- This function (sorry for the confusing name) differs from 'uniqueOf' in that
|
|
|
|
-- it only compares /sequentially/ duplicate elements (and thus operates in
|
|
|
|
-- linear time).
|
|
|
|
-- cf 'Data.Vector.uniq' and POSIX @uniq@ for the name
|
|
|
|
--
|
|
|
|
-- >>> uniqOf (folded . _2) ([(1, 2), (2, 2), (3, 1), (4, 2)] :: [(Int, Int)]) :: [Int]
|
|
|
|
-- [2,1,2]
|
|
|
|
--
|
|
|
|
-- @
|
|
|
|
-- 'uniqOf' :: Eq a => 'Getter' s a -> s -> [a]
|
|
|
|
-- 'uniqOf' :: Eq a => 'Fold' s a -> s -> [a]
|
|
|
|
-- 'uniqOf' :: Eq a => 'Lens'' s a -> s -> [a]
|
|
|
|
-- 'uniqOf' :: Eq a => 'Iso'' s a -> s -> [a]
|
|
|
|
-- 'uniqOf' :: Eq a => 'Traversal'' s a -> s -> [a]
|
|
|
|
-- 'uniqOf' :: Eq a => 'Prism'' s a -> s -> [a]
|
|
|
|
-- @
|
|
|
|
uniqOf :: (Monoid c, Cons c c w w, Eq w) => Getting (Endo (Maybe w, c)) a w -> a -> c
|
|
|
|
uniqOf aFold = snd . foldrOf aFold rejectSeen (Nothing, mempty)
|
|
|
|
where
|
|
|
|
rejectSeen x (Nothing, acc) = (Just x, x <| acc)
|
|
|
|
rejectSeen x tup@(Just a, acc)
|
|
|
|
| x == a = tup
|
|
|
|
| otherwise = (Just x, x <| acc)
|
|
|
|
|
|
|
|
-- | O(n). Returns a monoidal, 'Cons'able container (a list, a Vector, etc.)
|
|
|
|
-- consisting of the targets of the given 'Foldable' container with sequential
|
|
|
|
-- duplicate elements removed
|
|
|
|
--
|
|
|
|
-- This function (sorry for the confusing name) differs from 'unique' in that
|
|
|
|
-- it only compares /sequentially/ unique elements (and thus operates in linear
|
|
|
|
-- time).
|
|
|
|
-- cf 'Data.Vector.uniq' and POSIX @uniq@ for the name
|
|
|
|
--
|
|
|
|
-- >>> uniq [1, 1, 1, 2, 2, 2, 3, 3, 1] :: [Int]
|
|
|
|
-- [1,2,3,1]
|
|
|
|
--
|
|
|
|
-- >>> uniq [1, 1, 1, 2, 2, 2, 3, 3, 1] :: Vector Int
|
|
|
|
-- [1,2,3,1]
|
|
|
|
--
|
|
|
|
uniq :: (Foldable f, Eq a, Cons c c a a, Monoid c) => f a -> c
|
|
|
|
uniq = uniqOf folded
|
|
|
|
|
|
|
|
-- | Like 'takeWhile', but inclusive
|
|
|
|
takeWhileInclusive :: (a -> Bool) -> [a] -> [a]
|
|
|
|
takeWhileInclusive _ [] = []
|
|
|
|
takeWhileInclusive p (x:xs) = x : if p x then takeWhileInclusive p xs else []
|
2019-10-06 18:50:29 +02:00
|
|
|
|
|
|
|
-- | Returns the smallest value not in a list
|
|
|
|
smallestNotIn :: (Ord a, Bounded a, Enum a) => [a] -> a
|
|
|
|
smallestNotIn xs = case uniq $ sort xs of
|
|
|
|
[] -> minBound
|
|
|
|
xs'@(x : _)
|
|
|
|
| x > minBound -> minBound
|
|
|
|
| otherwise
|
|
|
|
-> snd . headEx . filter (uncurry (/=)) $ zip (xs' ++ [minBound]) [minBound..]
|
2019-12-01 01:55:43 +01:00
|
|
|
|
2019-12-23 05:22:25 +01:00
|
|
|
-- | Remove the element at the given index, if any, from the given vector
|
|
|
|
removeVectorIndex :: Int -> Vector a -> Vector a
|
|
|
|
removeVectorIndex idx vect =
|
|
|
|
let (before, after) = V.splitAt idx vect
|
|
|
|
in before <> fromMaybe Empty (tailMay after)
|
|
|
|
|
2019-12-25 01:40:52 +01:00
|
|
|
maximum1 :: (Ord a, Foldable1 f) => f a -> a
|
|
|
|
maximum1 = getMax . foldMap1 Max
|
|
|
|
|
|
|
|
minimum1 :: (Ord a, Foldable1 f) => f a -> a
|
|
|
|
minimum1 = getMin . foldMap1 Min
|
|
|
|
|
2019-12-30 17:31:56 +01:00
|
|
|
times :: (Applicative f, Num n, Enum n) => n -> (n -> f b) -> f [b]
|
|
|
|
times n f = traverse f [1..n]
|
|
|
|
|
|
|
|
times_ :: (Applicative f, Num n, Enum n) => n -> f a -> f [a]
|
|
|
|
times_ n fa = times n (const fa)
|
|
|
|
|
2019-12-01 01:55:43 +01:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | This class gives a boolean associated with a type-level bool, a'la
|
|
|
|
-- 'KnownSymbol', 'KnownNat' etc.
|
|
|
|
class KnownBool (bool :: Bool) where
|
|
|
|
boolVal' :: forall proxy. proxy bool -> Bool
|
|
|
|
boolVal' _ = boolVal @bool
|
|
|
|
|
|
|
|
boolVal :: Bool
|
|
|
|
boolVal = boolVal' $ Proxy @bool
|
|
|
|
|
|
|
|
instance KnownBool 'True where boolVal = True
|
|
|
|
instance KnownBool 'False where boolVal = False
|