text-containers-0.1.0.0: src/Data/TextMap/Unboxed/Lazy.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeFamilies #-}
-- |
-- Copyright: © 2017 Herbert Valerio Riedel
-- License: GPLv3
--
-- This module provides the 'TextMap' container for storing maps of text keys to non-strict values.
--
-- This module is intended to be imported @qualified@, e.g.
--
-- > import Data.TextMap.Unboxed.Lazy (TextMap)
-- > import qualified Data.TextMap.Unboxed.Lazy as TextMap
--
-- The API of this module provides value-lazy operations.
module Data.TextMap.Unboxed.Lazy
( TextMap
, Key
-- * Querying & lookup
, size
, null
, member
, lookup
, (!?)
, findWithDefault
, lookupMin
, lookupMax
, lookupLE
, lookupGE
-- * Construction
, empty
, singleton
, fromList
, fromDistinctAscList
, fromMap
-- * Deconstruction
, toList
, toMap
-- * Traversals
, map
) where
import Control.DeepSeq
import qualified Data.Foldable as F
import Data.Hashable (Hashable (..))
import qualified Data.Map.Lazy as Map
import qualified GHC.Exts as GHC (IsList (..))
import Prelude hiding (lookup, map, null)
import Data.TextSet.Unboxed (Key, TextSet)
import qualified Data.TextSet.Unboxed as TS
import Internal
-- | A map of (unboxed) 'ShortText' string keys to values.
--
-- The memory footprint of this data-structure is expressed in words
--
-- \[
-- 8 + 2n + \left\lceil \frac{1}{w} \sum_{i=0}^{n-1} len(k_i) \right\rceil + \sum_{i=0}^{n-1} size(v_i)
-- \]
--
-- where
--
-- * the word-size \(w\) is either \(w = 4\) or \(w = 8\) bytes,
-- * \(len(k_i)\) denotes the UTF-8 size in bytes of the \(i\)-th key string, and
-- * \(size(v_i)\) denotes the allocated heap size of the \(i\)-th value in words.
--
-- __NOTE__: One word can be saved by unpacking 'TextMap' (i.e. via @{-\# UNPACK \#-}@) into another constructor.
data TextMap v = TM !TextSet !(A v)
deriving (Eq)
instance Ord v => Ord (TextMap v) where
compare a b = compare (toList a) (toList b)
instance Show v => Show (TextMap v) where
showsPrec p m = showsPrec p (toList m)
show m = show (toList m)
instance Read v => Read (TextMap v) where
readsPrec p = fmap (\(x,s) -> (fromList x,s)) . readsPrec p
instance GHC.IsList (TextMap v) where
type Item (TextMap v) = (Key,v)
fromList = fromList
toList = toList
instance NFData v => NFData (TextMap v) where
rnf (TM ks vs) = ks `deepseq` vs `deepseq` ()
instance Hashable v => Hashable (TextMap v) where
hashWithSalt s (TM ks vs) = s `hashWithSalt` ks `hashWithSalt` vs
instance Functor TextMap where
fmap = map
v <$ (TM ks vs) = TM ks (v <$ vs)
instance Foldable TextMap where
foldMap f = F.foldMap f . elems
foldr f g = F.foldr f g . elems
toList = elems
null = null
length = size
instance Traversable TextMap where
traverse f (TM ks vs) = TM ks <$> arrayTraverse f vs
-- | \(\mathcal{O}(\log n)\). Lookup value for given key.
--
-- >>> fromList [("bb",False),("cc",True)] !? "cc"
-- Just True
--
-- This is a 'flip'ped infix alias of 'lookup'.
(!?) :: TextMap v -> Key -> Maybe v
(!?) = flip lookup
-- | \(\mathcal{O}(\log n)\). Lookup value for given key.
--
-- >>> lookup "a" (fromList [("bb",False),("cc",True)])
-- Nothing
--
-- >>> lookup "c" (fromList [("bb",False),("cc",True)])
-- Nothing
--
-- >>> lookup "cc" (fromList [("bb",False),("cc",True)])
-- Just True
--
-- >>> lookup "z" (fromList [("bb",False),("cc",True)])
-- Nothing
--
lookup :: Key -> TextMap v -> Maybe v
lookup k (TM ks vs) = indexArray vs <$> TS.lookupIndex k ks
-- | \(\mathcal{O}(1)\). Extract minimal key and its associated value.
--
-- >>> lookupMin empty
-- Nothing
--
-- >>> lookupMin (fromList [("a",EQ),("b",GT),("c",LT)])
-- Just ("a",EQ)
--
lookupMin :: TextMap v -> Maybe (Key,v)
lookupMin (TM ks vs) = case TS.lookupMin ks of
Nothing -> Nothing
Just k -> Just (k,indexArray vs 0)
-- | \(\mathcal{O}(1)\). Extract maximal key and its associated value.
--
-- >>> lookupMax empty
-- Nothing
--
-- >>> lookupMax (fromList [("a",EQ),("b",GT),("c",LT)])
-- Just ("c",LT)
--
lookupMax :: TextMap v -> Maybe (Key,v)
lookupMax (TM ks vs) = case TS.lookupMax ks of
Nothing -> Nothing
Just k -> Just (k,indexArray vs (sizeofArray vs - 1))
-- | \(\mathcal{O}(\log n)\). Lookup \"greatest\" key in map less or equal to given key and return key/value pair.
--
-- >>> lookupLE "a" (fromList [("bb",False),("cc",True)])
-- Nothing
--
-- >>> lookupLE "c" (fromList [("bb",False),("cc",True)])
-- Just ("bb",False)
--
-- >>> lookupLE "cc" (fromList [("bb",False),("cc",True)])
-- Just ("cc",True)
--
-- >>> lookupLE "z" (fromList [("bb",False),("cc",True)])
-- Just ("cc",True)
--
lookupLE :: Key -> TextMap v -> Maybe (Key,v)
lookupLE k0 (TM ks vs) = case TS.lookupLE k0 ks of
Nothing -> Nothing
Just (i,k) -> Just (k,indexArray vs i)
-- | \(\mathcal{O}(\log n)\). Lookup \"least\" key in map less or equal to given key and return key/value pair.
--
-- >>> lookupGE "a" (fromList [("bb",False),("cc",True)])
-- Just ("bb",False)
--
-- >>> lookupGE "c" (fromList [("bb",False),("cc",True)])
-- Just ("cc",True)
--
-- >>> lookupGE "cc" (fromList [("bb",False),("cc",True)])
-- Just ("cc",True)
--
-- >>> lookupGE "z" (fromList [("bb",False),("cc",True)])
-- Nothing
--
lookupGE :: Key -> TextMap v -> Maybe (Key,v)
lookupGE k0 (TM ks vs) = case TS.lookupGE k0 ks of
Nothing -> Nothing
Just (i,k) -> Just (k,indexArray vs i)
-- | \(\mathcal{O}(\log n)\). Lookup value for key in map or return default value if key not contained in map.
--
-- >>> findWithDefault True "z" (fromList [("bb",False),("cc",True)])
-- True
--
-- >>> findWithDefault True "bb" (fromList [("bb",False),("cc",True)])
-- False
--
-- See also 'lookup' or '!?'.
findWithDefault :: v -> Key -> TextMap v -> v
findWithDefault def k (TM ks vs) = maybe def (indexArray vs) (TS.lookupIndex k ks)
-- | \(\mathcal{O}(n)\). Convert 'Map' to 'TextMap'.
fromMap :: Map.Map Key v -> TextMap v
fromMap m0 = TM ks vs
where
ks = TS.fromSet (Map.keysSet m0)
vs = arrayFromListN (TS.size ks) (Map.elems m0)
-- | \(\mathcal{O}(n)\). Convert 'TextMap' to 'Map'.
toMap :: TextMap v -> Map.Map Key v
toMap m = Map.fromDistinctAscList (toList m)
-- | \(\mathcal{O}(1)\). Extract set of all keys.
keysTextSet :: TextMap v -> TextSet
keysTextSet (TM ks _) = ks
-- | \(\mathcal{O}(\log n)\). Test whether key is present in map.
--
-- >>> member "empty" empty
-- False
--
-- >>> member "a" (fromList [("a",EQ),("b",GT),("c",LT)])
-- True
--
-- >>> member "d" (fromList [("a",EQ),("b",GT),("c",LT)])
-- False
--
member :: Key -> TextMap v -> Bool
member k m = TS.member k (keysTextSet m)
-- | \(\mathcal{O}(n)\). list all keys in ascending order.
keys :: TextMap v -> [Key]
keys m = TS.toList (keysTextSet m)
-- | \(\mathcal{O}(n)\). list all values of the map in ascending order of their keys.
elems :: TextMap v -> [v]
elems (TM _ vs) = arrayToList vs
-- | \(\mathcal{O}(n)\). list all key/value pairs of the map in ascending order of their keys.
--
-- >>> toList (fromList [("Hey",GT),("Jude",EQ),("Hey",LT),("Law",EQ),("Hey",EQ),("",EQ)])
-- [("",EQ),("Hey",EQ),("Jude",EQ),("Law",EQ)]
--
toList :: TextMap v -> [(Key,v)]
toList m = zip (keys m) (elems m)
-- | \(\mathcal{O}(n \log n)\). Construct map from list of key/value pairs.
--
-- >>> toList (fromList [("Hey",GT),("Jude",EQ),("Hey",LT),("Law",EQ),("Hey",EQ),("",EQ)])
-- [("",EQ),("Hey",EQ),("Jude",EQ),("Law",EQ)]
--
fromList :: [(Key,v)] -> TextMap v
fromList [] = empty
fromList [(k,v)] = singleton k v
fromList kvs = fromMap (Map.fromList kvs)
-- | \(\mathcal{O}(n)\). Construct map from list with distinct keys in ascending order.
--
-- __NOTE__: If the input list is not strictly ascending, an 'error' is thrown.
fromDistinctAscList :: [(Key,v)] -> TextMap v
fromDistinctAscList as = TM ks vs
where
(ks0,vs0) = unzip as
ks = TS.fromDistinctAscList ks0
vs = arrayFromListN (TS.size ks) vs0
-- | \(\mathcal{O}(1)\). An empty 'TextMap'.
empty :: TextMap v
empty = TM TS.empty emptyA
-- | \(\mathcal{O}(1)\). Construct map containing a single entry.
--
-- >>> toList (singleton "sa" LT)
-- [("sa",LT)]
--
singleton :: Key -> v -> TextMap v
singleton k v = TM (TS.singleton k) (arraySingleton v)
-- | \(\mathcal{O}(1)\). Report number of entries in map.
--
-- >>> size empty
-- 0
--
-- >>> size (singleton "sa" LT)
-- 1
--
-- >>> size (fromList [("a",True),("b",False),("a",False)])
-- 2
--
size :: TextMap v -> Int
size (TM _ vs) = sizeofArray vs
-- | \(\mathcal{O}(1)\). Test whether map is 'empty'.
--
-- >>> null empty
-- True
--
-- >>> null (singleton "" ())
-- False
--
null :: TextMap v -> Bool
null m = size m == 0
-- | \(\mathcal{O}(n)\). Apply function to values in map.
--
-- >>> toList (map not (fromList [("a",True),("b",False),("a",False)]))
-- [("a",True),("b",True)]
--
-- This is a specialised version of 'fmap'.
map :: (v->w) -> TextMap v -> TextMap w
map f (TM ks vs) = TM ks (arrayMap f vs)
-- $setup
-- >>> :set -XOverloadedStrings