packages feed

word8set-0.1: src/Data/Word8Set.hs

{-# LANGUAGE BangPatterns          #-}
{-# LANGUAGE CPP                   #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE Trustworthy           #-}
{-# LANGUAGE TypeFamilies          #-}
-- | The 'Word8Set' type represents a set of elements of type 'Word8'.
--
-- The interface of this module mimics the "Data.Set" and/or "Data.IntSet"
-- module interfaces from @containers package.
--
-- These module is intended to be imported qualified, to avoid name clashes with Prelude functions, e.g.
--
-- @
-- import Data.Word8Set (Word8Set)
-- import qualified Data.Word8Set as W8S
-- @
--
-- == Implementation
-- The implementation is based on 'Word256' type. 'Word8Set' is 256 bits.
--
module Data.Word8Set (
    -- * Set type
    Word8Set,
    Key,

    -- * Construction
    empty,
    full,
    singleton,
    range,

    -- * Insertion
    insert,

    -- * Deletion
    delete,

    -- * Generalized insertion/deletion
    alterF,

    -- * Query
    member,
    notMember,
    lookupLT,
    lookupGT,
    lookupLE,
    lookupGE,
    null,
    isFull,
    isSingleton,
    isRange,
    size,
    isSubsetOf,
    isProperSubsetOf,
    disjoint,

    -- * Combine
    union,
    unions,
    difference,
    (\\),
    intersection,
    complement,

    -- * Filter
    filter,
    partition,

    -- * Map
    map,

    -- * Folds
    foldr,
    foldl,

    -- ** Strict folds
    foldr',
    foldl',

    -- * Min\/Max
    findMin,
    findMax,
    deleteMin,
    deleteMax,
    maxView,
    minView,

    -- * Conversion
    -- ** to/fromList
    elems,
    toList,
    fromList,
    fromFoldable,

    -- ** to/from Word256
    toWord256,
    fromWord256,

    -- ** to/from ASCII Strings
    toASCII,
    fromASCII,
) where

import Prelude
       (Bool (..), Eq ((==)), Functor (fmap), Int, Maybe (..), Ord, Show (showsPrec), String, fromIntegral, fst, maybe, negate, not,
       otherwise, return, showParen, showString, snd, ($), (&&), (+), (-), (.), (/=), (<=), (>))

import Algebra.Lattice       (BoundedJoinSemiLattice (..), BoundedMeetSemiLattice (..), Lattice (..))
import Control.DeepSeq       (NFData (..))
import Data.Bits             ((.&.), (.|.))
import Data.Char             (chr, ord)
import Data.Monoid           (Monoid (..))
import Data.Semigroup        (Semigroup (..))
import Data.WideWord.Word256 (Word256 (..))
import Data.Word             (Word8)
import Test.QuickCheck       (Arbitrary (..), CoArbitrary (..), Function (..), functionMap)

import Language.Haskell.TH.Syntax (Lift (..))

import qualified Data.Bits     as Bits
import qualified Data.Foldable as F
import qualified GHC.Exts

-- $setup
-- >>> import Prelude (even, (+), flip)

-------------------------------------------------------------------------------
-- Types
-------------------------------------------------------------------------------

-- | A set of 'Word8' numbers.
--
-- Implemented using 'Word256'.
newtype Word8Set = W8S Word256
  deriving (Eq, Ord)

-- | Key of 'Word8Set' is 'Word8'.
type Key = Word8

-------------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------------

instance Show Word8Set where
    showsPrec d xs = showParen (d > 10) $ showString "fromList " . showsPrec 11 (toList xs)

instance NFData Word8Set where
    rnf (W8S xs) = rnf xs

instance Lift Word8Set where
    lift (W8S (Word256 a b c d)) =
        [| W8S (Word256 a b c d) |]

#if MIN_VERSION_template_haskell(2,16,0)
    liftTyped (W8S (Word256 a b c d)) =
        [|| W8S (Word256 a b c d) ||]
#endif

instance Semigroup Word8Set where
    (<>) = union

instance Monoid Word8Set where
    mempty = empty
    mappend = (<>)

instance Arbitrary Word8Set where
    arbitrary = do
        a <- arbitrary
        b <- arbitrary
        c <- arbitrary
        d <- arbitrary
        return (W8S (Word256 a b c d))

    shrink xs = fmap fromList (shrink (toList xs)) where

instance CoArbitrary Word8Set where
    coarbitrary (W8S (Word256 a b c d)) = coarbitrary (a,b,c,d)

instance Function Word8Set where
    function = functionMap from to where
        from (W8S (Word256 a b c d)) = (a, b, c, d)
        to (a,b,c,d) = W8S (Word256 a b c d)

instance Lattice Word8Set where
    (\/) = union
    (/\) = intersection

instance BoundedJoinSemiLattice Word8Set where
    bottom = empty

instance BoundedMeetSemiLattice Word8Set where
    top = full

instance GHC.Exts.IsList Word8Set where
  type Item Word8Set = Key
  fromList = fromList
  toList   = toList

-------------------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------------------

-- | The empty set.
--
-- >>> empty
-- fromList []
--
empty :: Word8Set
empty = W8S Bits.zeroBits

-- | The full set.
--
-- >>> full
-- fromList [0,1,2,3,4,5,6,7,8,9,10,11,12,...,255]
--
full :: Word8Set
full = W8S ones

ones :: Word256
ones = Bits.complement Bits.zeroBits

-- | A set of one element.
--
-- >>> singleton 127
-- fromList [127]
--
singleton :: Key -> Word8Set
singleton x = W8S (Bits.bit (fromIntegral x))

-- | A set of inclusive range.
--
-- >>> range 10 20
-- fromList [10,11,12,13,14,15,16,17,18,19,20]
--
range :: Key -> Key -> Word8Set
range mi ma
    | mi <= ma  = W8S $ Bits.shiftL (Bits.shiftR ones (fromIntegral (negate (1 + ma - mi)))) (fromIntegral mi)
    | otherwise = empty

-------------------------------------------------------------------------------
-- Insertion
-------------------------------------------------------------------------------

-- | Add a value to the set.
--
-- >>> insert 10 (range 15 20)
-- fromList [10,15,16,17,18,19,20]
--
-- >>> insert 10 (range 10 15)
-- fromList [10,11,12,13,14,15]
--
insert :: Key -> Word8Set -> Word8Set
insert x (W8S xs) = W8S (Bits.setBit xs (fromIntegral x))

-------------------------------------------------------------------------------
-- Deletion
-------------------------------------------------------------------------------

-- | Delete a value in the set. Returns the original set when the value was not present.
--
-- >>> delete 10 (range 5 15)
-- fromList [5,6,7,8,9,11,12,13,14,15]
--
-- >>> delete 10 (range 1 10)
-- fromList [1,2,3,4,5,6,7,8,9]
--
delete :: Key -> Word8Set -> Word8Set
delete x (W8S xs) = W8S (Bits.clearBit xs (fromIntegral x))

-------------------------------------------------------------------------------
-- Generalized insertion/deletion
-------------------------------------------------------------------------------

-- | Generalized insetion deletion.
alterF :: Functor f => (Bool -> f Bool) -> Key -> Word8Set -> f Word8Set
alterF f x xs
    | member x xs = fmap (\b -> if b then xs else delete x xs) (f True)
    | otherwise   = fmap (\b -> if b then insert x xs else xs) (f False)

-------------------------------------------------------------------------------
-- Query
-------------------------------------------------------------------------------

-- | Is the set empty?
--
-- >>> null empty
-- True
--
-- >>> null (range 10 20)
-- False
--
null :: Word8Set -> Bool
null (W8S xs) = xs == Bits.zeroBits

-- | Is the set full?
--
-- >>> isFull full
-- True
--
-- >>> isFull (range 1 255)
-- False
--
isFull :: Word8Set -> Bool
isFull (W8S xs) = xs == ones

-- | Cardinality of the set.
--
-- >>> size empty
-- 0
--
-- >>> size (union (range 10 20) (range 30 40))
-- 22
--
size :: Word8Set -> Int
size (W8S xs) = Bits.popCount xs

-- | Is the value a member of the set?
--
-- >>> member 5 (range 10 20)
-- False
--
-- >>> member 15 (range 10 20)
-- True
--
member :: Key -> Word8Set -> Bool
member x (W8S xs) = Bits.testBit xs (fromIntegral x)

-- | Is the element not in the set?
--
-- >>> notMember 5 (range 10 20)
-- True
--
-- >>> notMember 15 (range 10 20)
-- False
--
notMember :: Key -> Word8Set -> Bool
notMember x xs = not (member x xs)

-- | Is this a subset? @(s1 `isSubsetOf` s2)@ tells whether s1 is a subset of s2.
--
-- >>> isSubsetOf (range 10 20) (range 5 25)
-- True
--
-- >>> isSubsetOf (range 10 20) (range 10 20)
-- True
--
-- >>> isSubsetOf (range 5 25) (range 10 20)
-- False
--
isSubsetOf :: Word8Set -> Word8Set -> Bool
isSubsetOf a b = b == union a b

-- | Is this a proper subset? Is this a proper subset? (ie. a subset but not equal).
--
-- >>> isProperSubsetOf (range 10 20) (range 5 25)
-- True
--
-- >>> isProperSubsetOf (range 10 20) (range 10 20)
-- False
--
-- >>> isProperSubsetOf (range 5 25) (range 10 20)
-- False
--
isProperSubsetOf :: Word8Set -> Word8Set -> Bool
isProperSubsetOf a b = b == union a b && a /= b

-- | Is set singleton?
--
-- >>> isSingleton empty
-- Nothing
--
-- >>> isSingleton full
-- Nothing
--
-- >>> isSingleton (singleton 5)
-- Just 5
--
-- >>> isSingleton (fromList [3, 5])
-- Nothing
--
isSingleton :: Word8Set -> Maybe Key
isSingleton (W8S ws)
    | Bits.popCount ws /= 1 = Nothing
    | otherwise             = Just (fromIntegral (ctz ws))

-- | Is set of the form @'range' l r@?
--
-- >>> isRange empty
-- Nothing
--
-- >>> isRange full
-- Just (0,255)
--
-- >>> isRange (singleton 5)
-- Just (5,5)
--
-- >>> isRange (range 10 20)
-- Just (10,20)
--
-- >>> isRange (fromList [3, 5])
-- Nothing
--
isRange :: Word8Set -> Maybe (Key, Key)
isRange (W8S 0)  = Nothing
isRange (W8S ws) = if Bits.popCount ws + l + r == 256 then Just (fromIntegral l, fromIntegral (255 - r)) else Nothing
  where
    r = clz ws
    l = ctz ws

-- | Check whether two sets are disjoint (i.e. their intersection is empty).
--
-- >>> disjoint (range 11 20) (range 21 30)
-- True
--
disjoint :: Word8Set -> Word8Set -> Bool
disjoint xs ys = intersection xs ys == empty

-- | Find largest element smaller than the given one.
--
-- >>> lookupLT 3 (fromList [3, 5])
-- Nothing
-- >>> lookupLT 5 (fromList [3, 5])
-- Just 3
--
-- >>> lookupLT 0 full
-- Nothing
lookupLT :: Key -> Word8Set -> Maybe Key
lookupLT 0 _  = Nothing
lookupLT x xs = lookupLE (x - 1) xs

-- | Find smallest element greater than the given one.
--
-- >>> lookupGT 4 (fromList [3, 5])
-- Just 5
--
-- >>> lookupGT 5 (fromList [3, 5])
-- Nothing
--
-- >>> lookupGT 255 full
-- Nothing
--
lookupGT :: Key -> Word8Set -> Maybe Key
lookupGT 255 _  = Nothing
lookupGT x   xs = lookupGE (x + 1) xs

-- | Find largest element smaller or equal to the given one.
--
-- >>> lookupLE 2 (fromList [3, 5])
-- Nothing
--
-- >>> lookupLE 4 (fromList [3, 5])
-- Just 3
--
-- >>> lookupLE 5 (fromList [3, 5])
-- Just 5
--
lookupLE :: Key -> Word8Set -> Maybe Key
lookupLE x xs = fmap fst (maxView (intersection xs (range 0 x)))

-- | Find smallest element greater or equal to the given one.
--
-- >>> lookupGE 3 (fromList [3, 5])
-- Just 3
--
-- >>> lookupGE 4 (fromList [3, 5])
-- Just 5
--
-- >>> lookupGE 6 (fromList [3, 5])
-- Nothing
--
lookupGE :: Key -> Word8Set -> Maybe Key
lookupGE x xs = fmap fst (minView (intersection xs (range x 255)))

-------------------------------------------------------------------------------
-- Combine
-------------------------------------------------------------------------------

-- | The complement of the set.
complement :: Word8Set -> Word8Set
complement (W8S xs) = W8S (Bits.complement xs)

-- | The union of two sets.
union :: Word8Set -> Word8Set -> Word8Set
union (W8S xs) (W8S ys) = W8S (xs .|. ys)

-- | The union of a list of sets.
unions :: F.Foldable f => f Word8Set -> Word8Set
unions xs = F.foldl' union empty xs

-- | The intersection between two sets.
intersection :: Word8Set -> Word8Set -> Word8Set
intersection (W8S xs) (W8S ys) = W8S (xs .&. ys)

-- | Difference between two sets.
difference :: Word8Set -> Word8Set -> Word8Set
difference xs ys = intersection xs (complement ys)

-- | See 'difference'.
(\\) :: Word8Set -> Word8Set -> Word8Set
m1 \\ m2 = difference m1 m2

-------------------------------------------------------------------------------
-- Filter
-------------------------------------------------------------------------------

-- | Filter all elements that satisfy some predicate.
--
-- >>> filter even (range 10 20)
-- fromList [10,12,14,16,18,20]
--
filter :: (Key -> Bool) -> Word8Set -> Word8Set
filter p = foldl' (\acc x -> if p x then insert x acc else acc) empty

-- | Partition the set according to some predicate.
--
-- >>> partition even (range 10 20)
-- (fromList [10,12,14,16,18,20],fromList [11,13,15,17,19])
--
partition :: (Key -> Bool) -> Word8Set -> (Word8Set,Word8Set)
partition p = foldl' (\(l, r) x -> if p x then (insert x l, r) else (l, insert x r)) (empty, empty)

-------------------------------------------------------------------------------
-- Map
-------------------------------------------------------------------------------

-- |  @'map' f s@ is the set obtained by applying @f@ to each element of @s@.
--
-- >>> map (+ 1) (fromList [0, 10, 250, 255])
-- fromList [0,1,11,251]
--
map :: (Key -> Key) -> Word8Set -> Word8Set
map f = foldl' (\acc k -> insert (f k) acc) empty

-------------------------------------------------------------------------------
-- Folds
-------------------------------------------------------------------------------

-- | Lazy right fold.
--
-- >>> foldr (:) [] (unions [range 10 20, range 70 73, range 130 132, range 200 202, singleton 255])
-- [10,11,12,13,14,15,16,17,18,19,20,70,71,72,73,130,131,132,200,201,202,255]
--
foldr :: (Key -> b -> b) -> b -> Word8Set -> b
foldr f z0 (W8S (Word256 a0 b0 c0 d0)) = go0 a0 b0 c0 d0 z0 where
    go0 0 !b !c !d acc = go1 b c d acc
    go0 a  b  c  d acc = let !x = clz a in go0 (Bits.clearBit a (63 - x)) b c d (f (255 - fromIntegral x) acc)

    go1 0 !c d acc = go2 c d acc
    go1 b  c d acc = let !x = clz b in go1 (Bits.clearBit b (63 - x)) c d (f (191 - fromIntegral x) acc)

    go2 0 !d acc = go3 d acc
    go2 c  d acc = let !x = clz c in go2 (Bits.clearBit c (63 - x)) d (f (127 - fromIntegral x) acc)

    go3 0 acc = acc
    go3 a acc = let !x = clz a in go3 (Bits.clearBit a (63 - x)) (f (63 - fromIntegral x) acc)

-- | Strict right fold.
--
foldr' :: (Key -> b -> b) -> b -> Word8Set -> b
foldr' f z0 (W8S (Word256 a0 b0 c0 d0)) = go0 a0 b0 c0 d0 z0 where
    go0 0 !b !c !d !acc = go1 b c d acc
    go0 a  b  c  d  acc = let !x = clz a in go0 (Bits.clearBit a (63 - x)) b c d (f (255 - fromIntegral x) acc)

    go1 0 !c d !acc = go2 c d acc
    go1 b  c d  acc = let !x = clz b in go1 (Bits.clearBit b (63 - x)) c d (f (191 - fromIntegral x) acc)

    go2 0 !d !acc = go3 d acc
    go2 c  d  acc = let !x = clz c in go2 (Bits.clearBit c (63 - x)) d (f (127 - fromIntegral x) acc)

    go3 0 !acc = acc
    go3 a  acc = let !x = clz a in go3 (Bits.clearBit a (63 - x)) (f (63 - fromIntegral x) acc)

-- | Lazy left fold.
--
-- >>> foldl (flip (:)) [] (unions [range 10 20, range 70 73, range 130 132, range 200 202, singleton 255])
-- [255,202,201,200,132,131,130,73,72,71,70,20,19,18,17,16,15,14,13,12,11,10]
--
foldl :: (a -> Key -> a) -> a -> Word8Set -> a
foldl f z0 (W8S (Word256 a0 b0 c0 d0)) = go0 a0 b0 c0 d0 z0 where
    go0 !a !b !c 0 acc = go1 a b c acc
    go0  a  b  c d acc = let !x = ctz d in go0 a b c (Bits.clearBit d x) (f acc (fromIntegral x))

    go1 !a !b 0 acc = go2 a b acc
    go1  a  b c acc = let !x = ctz c in go1 a b (Bits.clearBit c x) (f acc (fromIntegral x + 64))

    go2 !a 0 acc = go3 a acc
    go2  a b acc = let !x = ctz b in go2 a (Bits.clearBit b x) (f acc (fromIntegral x + 128))

    go3 0 acc = acc
    go3 a acc = let !x = ctz a in go3 (Bits.clearBit a x) (f acc (fromIntegral x + 192))

-- | Strict left fold.
--
foldl' :: (a -> Key -> a) -> a -> Word8Set -> a
foldl' f z0 (W8S (Word256 a0 b0 c0 d0)) = go0 a0 b0 c0 d0 z0 where
    go0 !a !b !c 0 !acc = go1 a b c acc
    go0  a  b  c d  acc = let !x = ctz d in go0 a b c (Bits.clearBit d x) (f acc (fromIntegral x))

    go1 !a !b 0 !acc = go2 a b acc
    go1  a  b c  acc = let !x = ctz c in go1 a b (Bits.clearBit c x) (f acc (fromIntegral x + 64))

    go2 !a 0 !acc = go3 a acc
    go2  a b  acc = let !x = ctz b in go2 a (Bits.clearBit b x) (f acc (fromIntegral x + 128))

    go3 0 !acc = acc
    go3 a  acc = let !x = ctz a in go3 (Bits.clearBit a x) (f acc (fromIntegral x + 192))

-------------------------------------------------------------------------------
-- Min/Max
-------------------------------------------------------------------------------

-- | The minimal element of the set.
--
-- >>> findMin (fromList [3, 5])
-- 3
--
-- Returns 0 for empty set.
--
-- >>> findMin empty
-- 0
--
findMin :: Word8Set -> Word8
findMin (W8S xs) = fromIntegral (ctz xs)

-- | The maximal element of the set.
--
-- >>> findMax (fromList [3, 5])
-- 5
--
-- Returns 255 for empty set.
--
-- >>> findMax empty
-- 255
--
findMax :: Word8Set -> Word8
findMax (W8S xs) = fromIntegral (255 - clz xs)

-- | Delete the minimal element. Returns an empty set if the set is empty.
deleteMin :: Word8Set -> Word8Set
deleteMin = maybe empty snd . minView

-- | Delete the maximal element. Returns an empty set if the set is empty.
deleteMax :: Word8Set -> Word8Set
deleteMax = maybe empty snd . minView

-- | Retrieves the maximal key of the set, and the set
-- stripped of that element, or 'Nothing' if passed an empty set.
--
-- >>> maxView (fromList [3, 5])
-- Just (5,fromList [3])
--
-- >>> maxView empty
-- Nothing
--
maxView :: Word8Set -> Maybe (Key, Word8Set)
maxView xs
    | null xs   = Nothing
    | otherwise = let !x = findMax xs in Just (x, delete x xs)

-- | Retrieves the minimal key of the set, and the set
-- stripped of that element, or 'Nothing' if passed an empty set.
--
-- >>> minView (fromList [3, 5])
-- Just (3,fromList [5])
--
-- >>> minView empty
-- Nothing
--
minView :: Word8Set -> Maybe (Key, Word8Set)
minView xs
    | null xs   = Nothing
    | otherwise = let !x = findMin xs in Just (x, delete x xs)

-------------------------------------------------------------------------------
-- List
-------------------------------------------------------------------------------

-- | The elements of a set in ascending order.
elems :: Word8Set -> [Key]
elems = toList

-- | The elements of a set in ascending order.
toList :: Word8Set -> [Key]
toList xs = GHC.Exts.build (\c n -> foldr c n xs)

-- | Create a set from a list of 'Word8's.
fromList :: [Key] -> Word8Set
fromList w8s = F.foldl' (\acc x -> insert x acc) empty w8s

-- | Create a set from a foldable of 'Word8's.
fromFoldable :: F.Foldable f => f Key -> Word8Set
#if MIN_VERSION_base(4,13,0)
fromFoldable = F.foldMap' singleton
#else
fromFoldable = F.foldl' (\acc x -> insert x acc) empty
#endif

-------------------------------------------------------------------------------
-- Word256
-------------------------------------------------------------------------------

-- | Convert set to 'Word256'.
toWord256 :: Word8Set -> Word256
toWord256 (W8S xs) = xs

-- | Create set from 'Word256'.
fromWord256 :: Word256 -> Word8Set
fromWord256 = W8S

-------------------------------------------------------------------------------
-- ASCII
-------------------------------------------------------------------------------

-- | Create ASCII string from a set.
--
-- >>> toASCII (range 100 120)
-- "defghijklmnopqrstuvwx"
--
toASCII :: Word8Set -> String
toASCII = fmap (chr . fromIntegral) . toList

-- | Create set from ASCII string.
--
-- >>> fromASCII "foobar"
-- fromList [97,98,102,111,114]
--
-- Non-ASCII codepoints are truncated:
--
-- >>> fromASCII "\1000"
-- fromList [232]
--
fromASCII :: String -> Word8Set
fromASCII = fromList .  fmap (fromIntegral . ord)

-------------------------------------------------------------------------------
-- Helpers
-------------------------------------------------------------------------------

clz :: Bits.FiniteBits a => a -> Int
clz = Bits.countLeadingZeros
{-# INLINE clz #-}

ctz :: Bits.FiniteBits a => a -> Int
ctz = Bits.countTrailingZeros
{-# INLINE ctz #-}