kleene-0: src/Kleene/Internal/Partition.hs
{-# LANGUAGE Safe #-}
module Kleene.Internal.Partition where
import Prelude ()
import Prelude.Compat
import Data.Foldable (toList)
import Data.List.NonEmpty.Compat (NonEmpty (..))
import Data.RangeSet.Map (RSet)
import Data.Set (Set)
import qualified Data.Function.Step.Discrete.Closed as SF
import qualified Data.List.NonEmpty.Compat as NE
import qualified Data.RangeSet.Map as RSet
import qualified Data.Set as Set
import Test.QuickCheck
-- | 'Partition' devides type into disjoint connected partitions.
--
-- /Note:/ we could have non-connecter partitions too,
-- but that would be more complicated.
-- This variant is correct by construction, but less precise.
--
-- It's enought to store last element of each piece.
--
-- @'Partition' (fromList [x1, x2, x3]) :: 'Partition' s@ describes a partition of /Set/ @s@, as
--
-- \[
-- \{ x \mid x \le x_1 \} \cup
-- \{ x \mid x_1 < x \le x_2 \} \cup
-- \{ x \mid x_2 < x \le x_3 \} \cup
-- \{ x \mid x_3 < x \}
-- \]
--
-- /Note:/ it's enough to check upper bound conditions only if checks are performed in order.
--
-- /Invariant:/ 'maxBound' is not in the set.
--
newtype Partition a = Partition { unPartition :: Set a }
deriving (Eq, Ord)
-- | Check invariant.
invariant :: (Ord a, Bounded a) => Partition a -> Bool
invariant (Partition xs) = Set.notMember maxBound xs
-------------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------------
instance Show a => Show (Partition a) where
showsPrec d (Partition xs)
= showParen (d > 10)
$ showString "fromSeparators "
. showsPrec 11 (Set.toList xs)
-- | prop> invariant (asPartitionChar p)
instance (Enum a, Bounded a, Ord a, Arbitrary a) => Arbitrary (Partition a) where
arbitrary = fromSeparators <$> arbitrary
-- | See 'wedge'.
instance (Enum a, Bounded a, Ord a) => Semigroup (Partition a) where
(<>) = wedge
instance (Enum a, Bounded a, Ord a) => Monoid (Partition a) where
mempty = whole
mappend = (<>)
-------------------------------------------------------------------------------
-- Constructors
-------------------------------------------------------------------------------
fromSeparators :: (Enum a, Bounded a, Ord a) => [a] -> Partition a
fromSeparators = Partition . Set.fromList . filter (/= maxBound)
-- | Construct 'Partition' from list of 'RSet's.
--
-- RSet intervals are closed on both sides.
fromRSets :: (Enum a, Bounded a, Ord a) => [RSet a] -> Partition a
fromRSets rs = Partition $ Set.fromList $ concat
[ (if x == minBound then [] else [pred x]) ++
(if y == maxBound then [] else [y])
| r <- rs
, (x, y) <- RSet.toRangeList r
]
fromRSet :: (Enum a, Bounded a, Ord a) => RSet a -> Partition a
fromRSet r
| r == RSet.empty = whole
| r == RSet.full = whole
| otherwise = fromRSets [r]
whole :: Partition a
whole = Partition Set.empty
-------------------------------------------------------------------------------
-- Querying
-------------------------------------------------------------------------------
-- | Count of sets in a 'Partition'.
--
-- >>> size whole
-- 1
--
-- >>> size $ split (10 :: Word8)
-- 2
--
-- prop> size (asPartitionChar p) >= 1
--
size :: Partition a -> Int
size (Partition xs) = 1 + length xs
-- | Extract examples from each subset in a 'Partition'.
--
-- >>> examples $ split (10 :: Word8)
-- fromList [10,255]
--
-- >>> examples $ split (10 :: Word8) <> split 20
-- fromList [10,20,255]
--
-- prop> invariant p ==> size (asPartitionChar p) === length (examples p)
--
examples :: (Bounded a, Enum a, Ord a) => Partition a -> Set a
examples (Partition xs) = Set.insert maxBound xs
-- |
--
-- prop> all (curry (<=)) $ intervals $ asPartitionChar p
intervals :: (Enum a, Bounded a, Ord a) => Partition a -> NonEmpty (a, a)
intervals (Partition xs) = go minBound (toList xs) where
go x [] = (x, maxBound) :| []
go x (y : ys) = (x, y) `NE.cons` go y ys
-------------------------------------------------------------------------------
--
-- Operations
-------------------------------------------------------------------------------
-- | Wedge partitions.
--
-- >>> split (10 :: Word8) <> split 20
-- fromSeparators [10,20]
--
-- prop> whole `wedge` (p :: Partition Char) === p
-- prop> (p :: Partition Char) <> whole === p
-- prop> asPartitionChar p <> q === q <> p
-- prop> asPartitionChar p <> p === p
-- prop> invariant $ asPartitionChar p <> q
--
wedge :: Ord a => Partition a -> Partition a -> Partition a
wedge (Partition as) (Partition bs) = Partition (Set.union as bs)
-- | Simplest partition: given @x@ partition space into @[min..x) and [x .. max]@
--
-- >>> split (128 :: Word8)
-- fromSeparators [128]
--
split :: (Enum a, Bounded a, Eq a) => a -> Partition a
split x
| x == minBound = Partition Set.empty
| otherwise = Partition (Set.singleton x)
-------------------------------------------------------------------------------
-- Conversion
-------------------------------------------------------------------------------
-- | Make a step function.
toSF :: (Enum a, Bounded a, Ord a) => (a -> b) -> Partition a -> SF.SF a b
toSF f (Partition p) = SF.fromList
(map (\k -> (k, f k)) $ toList as)
(f maxBound)
where
as = toList p
-------------------------------------------------------------------------------
-- Doctest
-------------------------------------------------------------------------------
-- $setup
-- >>> import Data.Word
-- >>> import Test.QuickCheck ((===))
--
-- >>> let asPartitionChar :: Partition Char -> Partition Char; asPartitionChar = id
-- >>> instance (Ord a, Enum a, Arbitrary a) => Arbitrary (RSet a) where arbitrary = fmap RSet.fromRangeList arbitrary