{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TypeFamilies #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Multimap.Table -- Maintainer : Ziyang Liu <free@cofree.io> -- -- The @'Table' r c a@ type represents a finite two-dimensional table -- that associates a pair of keys (a row key of type @r@ and -- a column key of type @c@) with a value of type @a@. -- -- The implementation is backed by two maps: a @'Map' r ('Map' c) a@, and -- a @'Map' c ('Map' r) a@, called "row map" and "column map", respectively. -- -- It is worth noting that all functions that traverse a table, such as -- 'foldl', 'foldr', 'foldMap' and 'traverse', are row-oriented, i.e., -- they traverse the table row by row. To traverse a table column -- by column, 'transpose' the table first. -- -- In the following Big-O notations, unless otherwise noted, /n/ denotes -- the size of the table (i.e., the total number of values for all -- row and column keys), /r/ denotes the number of row keys that has at -- least one value, /c/ denotes the number of column keys that has at -- least one value, and /k = max r c/. module Data.Multimap.Table ( Table -- * Construction , empty , singleton , fromRowMap , fromColumnMap , transpose -- ** From Unordered Lists , fromList -- * Deletion\/Update , insert , delete , deleteRow , deleteColumn , adjust , adjustWithKeys , update , updateWithKeys , alter , alterWithKeys -- * Query -- ** Lookup , lookup , (!?) , (!) , hasCell , hasRow , hasColumn -- ** Size , null , notNull , size -- * Combine -- ** Union , union , unionWith , unionWithKeys , unions , unionsWith , unionsWithKeys -- ** Difference , difference -- * Traversal -- ** Map , map , mapWithKeys , traverseWithKeys , traverseMaybeWithKeys -- ** Folds , foldr , foldl , foldrWithKeys , foldlWithKeys , foldMapWithKeys -- ** Strict Folds , foldr' , foldl' , foldrWithKeys' , foldlWithKeys' -- * Conversion , row , column , rowMap , columnMap , rowKeys , columnKeys , rowKeysSet , columnKeysSet -- ** Lists , toList -- ** Ordered lists , toRowAscList , toColumnAscList , toRowDescList , toColumnDescList -- * Filter , filter , filterRow , filterColumn , filterWithKeys , mapMaybe , mapMaybeWithKeys , mapEither , mapEitherWithKeys ) where import Control.Arrow ((&&&)) import Data.Data (Data) import qualified Data.Foldable as Foldable import Data.Map (Map) import qualified Data.Map as Map import qualified Data.Maybe as Maybe import Data.Set (Set) import Prelude hiding (filter, foldl, foldr, lookup, map, null) infixl 9 !,!? type Size = Int newtype Table r c a = Table (Map r (Map c a), Map c (Map r a), Size) deriving (Eq, Ord, Data) instance (Show r, Show c, Show a) => Show (Table r c a) where showsPrec d m = showParen (d > 10) $ showString "fromList " . shows (toList m) instance (Ord r, Ord c, Read r, Read c, Read a) => Read (Table r c a) where readsPrec p = readParen (p > 10) $ \ r -> do ("fromList",s) <- lex r (xs,t) <- reads s pure (fromList xs,t) instance Functor (Table r c) where fmap = map instance Foldable.Foldable (Table r c) where foldMap = foldMapWithKeys . const . const {-# INLINE foldMap #-} instance (Ord r, Ord c) => Traversable (Table r c) where traverse = traverseWithKeys . const . const {-# INLINE traverse #-} instance (Ord r, Ord c) => Semigroup (Table r c a) where (<>) = union instance (Ord r, Ord c) => Monoid (Table r c a) where mempty = empty mappend = (<>) ------------------------------------------------------------------------------ -- | /O(1)/. The empty table. -- -- > size empty === 0 empty :: Table r c a empty = Table (Map.empty, Map.empty, 0) -- | /O(1)/. A table with a single element. -- -- > singleton 1 'a' "a" === fromList [(1,'a',"a")] -- > size (singleton 1 'a' "a") === 1 singleton :: r -> c -> a -> Table r c a singleton r c a = Table (Map.singleton r (Map.singleton c a), Map.singleton c (Map.singleton r a), 1) -- | Build a table from a list of key\/value pairs. -- -- > fromList ([] :: [(Int, Char, String)]) === empty fromList :: (Ord r, Ord c) => [(r, c, a)] -> Table r c a fromList = Foldable.foldr (uncurry3 insert) empty -- | Build a table from a row map. -- -- > fromRowMap (Map.fromList [(1, Map.fromList [('a',"b"),('b',"c")]), (2, Map.fromList [('a',"d")])]) -- > === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")] fromRowMap :: (Ord r, Ord c) => Map r (Map c a) -> Table r c a fromRowMap m = Table (m', transpose' m', size' m') where m' = nonEmpty m -- | Build a table from a column map. -- -- > fromColumnMap (Map.fromList [(1, Map.fromList [('a',"b"),('b',"c")]), (2, Map.fromList [('a',"d")])]) -- > === fromList [('a',1,"b"),('a',2,"d"),('b',1,"c")] fromColumnMap :: (Ord r, Ord c) => Map c (Map r a) -> Table r c a fromColumnMap m = Table (transpose' m', m', size' m') where m' = nonEmpty m -- | Flip the row and column keys. -- -- > transpose (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === fromList [('a',1,"b"),('a',2,"d"),('b',1,"c")] transpose :: Table r c a -> Table c r a transpose (Table (rm, cm, sz)) = Table (cm, rm, sz) ------------------------------------------------------------------------------ -- | /O(log k)/. Associate with value with the row key and the column key. -- If the table already contains a value for those keys, the value is replaced. -- -- > insert 1 'a' "a" empty === singleton 1 'a' "a" -- > insert 1 'a' "a" (fromList [(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'a',"a"),(1,'b',"c"),(2,'a',"d")] -- > insert 1 'a' "a" (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'a',"a"),(1,'b',"c"),(2,'a',"d")] insert :: (Ord r, Ord c) => r -> c -> a -> Table r c a -> Table r c a insert r c a (Table (rm, cm, _)) = fromMaps' r c rm' cm' where rm' = Map.alter f r rm cm' = Map.alter g c cm f = Just . maybe (Map.singleton c a) (Map.insert c a) g = Just . maybe (Map.singleton r a) (Map.insert r a) -- | /O(log k)/. Remove the value associated with the given keys. -- -- > delete 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'b',"c"),(2,'a',"d")] -- > delete 1 'a' (fromList [(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'b',"c"),(2,'a',"d")] delete :: (Ord r, Ord c) => r -> c -> Table r c a -> Table r c a delete r c (Table (rm, cm, _)) = fromMaps' r c rm' cm' where rm' = Map.adjust (Map.delete c) r rm cm' = Map.adjust (Map.delete r) c cm -- | Remove an entire row. -- -- > deleteRow 1 (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 2 'a' "d" -- > deleteRow 3 (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")] deleteRow :: Ord r => r -> Table r c a -> Table r c a deleteRow r (Table (rm, cm, _)) = Table (rm', cm', size' rm') where rm' = Map.delete r rm cm' = nonEmpty $ Map.map (Map.delete r) cm -- | Remove an entire column. -- -- > deleteColumn 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 1 'b' "c" -- > deleteColumn 'z' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")] deleteColumn :: Ord c => c -> Table r c a -> Table r c a deleteColumn c (Table (rm, cm, _)) = Table (rm', cm', size' cm') where rm' = nonEmpty $ Map.map (Map.delete c) rm cm' = Map.delete c cm -- | /O(log k)/, assuming the function @a -> a@ takes /O(1)/. -- Update the value at a specific row key and column key, if exists. -- -- > adjust ("new " ++) 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === fromList [(1,'a',"new b"),(1,'b',"c"),(2,'a',"d")] adjust :: (Ord r, Ord c) => (a -> a) -> r -> c -> Table r c a -> Table r c a adjust = adjustWithKeys . const . const -- | /O(log k)/, assuming the function @r -> c -> a -> a@ takes /O(1)/. -- Update the value at a specific row key and column key, if exists. -- -- > adjustWithKeys (\r c x -> show r ++ ":" ++ show c ++ ":new " ++ x) 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) -- > === fromList [(1,'a',"1:'a':new b"),(1,'b',"c"),(2,'a',"d")] adjustWithKeys :: (Ord r, Ord c) => (r -> c -> a -> a) -> r -> c -> Table r c a -> Table r c a adjustWithKeys f = updateWithKeys (\r c a -> Just (f r c a)) -- | /O(log k)/, assuming the function @a -> 'Maybe' a@ takes /O(1)/. -- The expression (@'update' f r c table@) updates the value at the given -- row and column keys, if exists. If @f@ returns 'Nothing', the value -- associated with those keys, if exists is deleted. -- -- > let f x = if x == "b" then Just "new b" else Nothing in do -- > update f 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"new b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > update f 1 'a' (fromList [(1,'a',"a"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] update :: (Ord r, Ord c) => (a -> Maybe a) -> r -> c -> Table r c a -> Table r c a update = updateWithKeys . const . const -- | /O(log k)/, assuming the function @r -> c -> a -> 'Maybe' a@ takes /O(1)/. -- The expression (@'updateWithKeys' f r c table@) updates the value at the given -- row and column keys, if exists. If @f@ returns 'Nothing', the value -- associated with those keys, if exists is deleted. -- -- > let f r c x = if x == "b" then Just (show r ++ ":" ++ show c ++ ":new b") else Nothing in do -- > updateWithKeys f 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"1:'a':new b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > updateWithKeys f 1 'a' (fromList [(1,'a',"a"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] updateWithKeys :: (Ord r, Ord c) => (r -> c -> a -> Maybe a) -> r -> c -> Table r c a -> Table r c a updateWithKeys f = alterWithKeys (\r c -> (>>= f r c)) -- | /O(log k)/, assuming the function @'Maybe' a -> 'Maybe' a@ takes /O(1)/. -- The expression (@'alter' f r c table@) alters the value at the given -- row and column keys, if exists. It can be used to insert, delete -- or update a value. -- -- > let (f,g,h) = (const Nothing, const (Just "hello"), fmap ('z':)) in do -- > alter f 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alter f 4 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alter f 2 'b' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alter g 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"hello"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alter g 4 'e' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c"),(4,'e',"hello")] -- > alter h 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"zb"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alter h 2 'b' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] alter :: (Ord r, Ord c) => (Maybe a -> Maybe a) -> r -> c -> Table r c a -> Table r c a alter = alterWithKeys . const . const -- | /O(log k)/, assuming the function @r -> c -> 'Maybe' a -> 'Maybe' a@ takes /O(1)/. -- The expression (@'alterWithKeys' f r c table@) alters the value at the given -- row and column keys, if exists. It can be used to insert, delete -- or update a value. -- -- > let (f,g) = (\_ _ _ -> Nothing, \r c -> fmap ((show r ++ ":" ++ show c ++ ":") ++)) in do -- > alterWithKeys f 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alterWithKeys f 4 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alterWithKeys f 2 'b' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alterWithKeys g 1 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"1:'a':b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] -- > alterWithKeys g 2 'b' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")]) === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(3,'a',"c")] alterWithKeys :: (Ord r, Ord c) => (r -> c -> Maybe a -> Maybe a) -> r -> c -> Table r c a -> Table r c a alterWithKeys f r c tbl@(Table (rm, cm, _)) | Just a <- f r c (lookup r c tbl) = let rm' = Map.alter (Just . maybe (Map.singleton c a) (Map.insert c a)) r rm cm' = Map.alter (Just . maybe (Map.singleton r a) (Map.insert r a)) c cm in fromMaps' r c rm' cm' | otherwise = delete r c tbl ------------------------------------------------------------------------------ -- | /O(log k)/. Lookup the values at a row key and column key in the map. lookup :: (Ord r, Ord c) => r -> c -> Table r c a -> Maybe a lookup r c (Table (rm, _, _)) = Map.lookup r rm >>= Map.lookup c -- | /O(log k)/. Lookup the values at a row key and column key in the map. -- -- > fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")] !? (1,'a') === Just "b" -- > fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")] !? (1,'c') === Nothing (!?) :: (Ord r, Ord c) => Table r c a -> (r, c) -> Maybe a (!?) = flip (uncurry lookup) -- | /O(log k)/. Lookup the values at a row key and column key in the map. -- Calls 'error' if the value does not exist. -- -- > fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")] ! (1,'a') === "b" (!) :: (Ord r, Ord c) => Table r c a -> (r, c) -> a (!) tbl keys = Maybe.fromMaybe (error "Table.!: cell does not exist") (tbl !? keys) -- | /O(log k)/. Is there a value associated with the given row and -- column keys? -- -- > hasCell (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) (1,'a') === True -- > hasCell (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) (1,'c') === False hasCell :: (Ord r, Ord c) => Table r c a -> (r, c) -> Bool hasCell (Table (rm, _, _)) (r, c) = maybe False (Map.member c) (Map.lookup r rm) -- | /O(log r)/. Is there a row with the given row key that has at least -- one value? -- -- > hasRow (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) 1 === True -- > hasRow (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) 3 === False hasRow :: Ord r => Table r c a -> r -> Bool hasRow (Table (rm, _, _)) r = Map.member r rm -- | /O(log c)/. Is there a column with the given column key that has at least -- one value? -- -- > hasColumn (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) 'a' === True -- > hasColumn (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) 'c' === False hasColumn :: Ord c => Table r c a -> c -> Bool hasColumn (Table (_, cm, _)) c = Map.member c cm -- | /O(1)/. Is the table empty? -- -- > Data.Multimap.Table.null empty === True -- > Data.Multimap.Table.null (singleton 1 'a' "a") === False null :: Table r c a -> Bool null (Table (rm, _, _)) = Map.null rm -- | /O(1)/. Is the table non-empty? -- -- > notNull empty === False -- > notNull (singleton 1 'a' "a") === True notNull :: Table r c a -> Bool notNull = not . null -- | The total number of values for all row and column keys. -- -- @size@ is evaluated lazily. Forcing the size for the first time takes up to -- /O(n)/ and subsequent forces take /O(1)/. -- -- > size empty === 0 -- > size (singleton 1 'a' "a") === 1 -- > size (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) === 3 size :: Table r c a -> Int size (Table (_, _, sz)) = sz ------------------------------------------------------------------------------ -- | Union two tables, preferring values from the first table -- upon duplicate keys. -- -- > union (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) (fromList [(1,'a',"c"),(2,'b',"d"),(3,'c',"e")]) -- > === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(2,'b',"d"),(3,'c',"e")] union :: (Ord r, Ord c) => Table r c a -> Table r c a -> Table r c a union = unionWith const -- | Union a number of tables, preferring values from the leftmost table -- upon duplicate keys. -- -- > unions [fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")], fromList [(1,'a',"c"),(2,'b',"d"),(3,'c',"e")]] -- > === fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b"),(2,'b',"d"),(3,'c',"e")] unions :: (Foldable f, Ord r, Ord c) => f (Table r c a) -> Table r c a unions = Foldable.foldr union empty -- | Union two tables with a combining function for duplicate keys. -- -- > unionWith (++) (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) (fromList [(1,'a',"c"),(2,'b',"d"),(3,'c',"e")]) -- > === fromList [(1,'a',"bc"),(1,'b',"c"),(2,'a',"b"),(2,'b',"d"),(3,'c',"e")] unionWith :: (Ord r, Ord c) => (a -> a -> a) -> Table r c a -> Table r c a -> Table r c a unionWith = unionWithKeys . const . const -- | Union two tables with a combining function for duplicate keys. -- -- > let f r c a a' = show r ++ ":" ++ show c ++ ":" ++ a ++ "|" ++ a' in do -- > unionWithKeys f (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) (fromList [(1,'a',"c"),(2,'b',"d"),(3,'c',"e")]) -- > === fromList [(1,'a',"1:'a':b|c"),(1,'b',"c"),(2,'a',"b"),(2,'b',"d"),(3,'c',"e")] unionWithKeys :: (Ord r, Ord c) => (r -> c -> a -> a -> a) -> Table r c a -> Table r c a -> Table r c a unionWithKeys f (Table (rm1, cm1, _)) (Table (rm2, cm2, _)) = fromMaps rm cm where rm = Map.unionWithKey (Map.unionWithKey . f) rm1 rm2 cm = Map.unionWithKey (Map.unionWithKey . flip f) cm1 cm2 -- | Union a number of tables with a combining function for duplicate keys. -- -- > unionsWith (++) [fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")], fromList [(1,'a',"c"),(2,'b',"d"),(3,'c',"e")]] -- > === fromList [(1,'a',"bc"),(1,'b',"c"),(2,'a',"b"),(2,'b',"d"),(3,'c',"e")] unionsWith :: (Foldable f, Ord r, Ord c) => (a -> a -> a) -> f (Table r c a) -> Table r c a unionsWith f = Foldable.foldr (unionWith f) empty -- | Union a number of tables with a combining function for duplicate keys. -- -- > let f r c a a' = show r ++ ":" ++ show c ++ ":" ++ a ++ "|" ++ a' in do -- > unionsWithKeys f [fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")], fromList [(1,'a',"c"),(2,'b',"d"),(3,'c',"e")]] -- > === fromList [(1,'a',"1:'a':b|c"),(1,'b',"c"),(2,'a',"b"),(2,'b',"d"),(3,'c',"e")] unionsWithKeys :: (Foldable f, Ord r, Ord c) => (r -> c -> a -> a -> a) -> f (Table r c a) -> Table r c a unionsWithKeys f = Foldable.foldr (unionWithKeys f) empty -- | Difference of two tables. Return values in the first table whose -- row and column keys do not have an associated value in the second table. -- -- > difference (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) (fromList [(1,'a',"c"),(1,'b',"d"),(2,'b',"b")]) -- > === singleton 2 'a' "b" difference :: (Ord r, Ord c) => Table r c a -> Table r c a -> Table r c a difference (Table (rm1, cm1, _)) (Table (rm2, cm2, _)) = fromMaps rm cm where rm = Map.differenceWith ((Just .) . Map.difference) rm1 rm2 cm = Map.differenceWith ((Just .) . Map.difference) cm1 cm2 ------------------------------------------------------------------------------ -- | /O(n)/, assuming the function @a -> b@ takes /O(1)/. -- Map a function over all values in the table. -- -- > Data.Multimap.Table.map (++ "x") (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) === fromList [(1,'a',"bx"),(1,'b',"cx"),(2,'a',"bx")] map :: (a -> b) -> Table r c a -> Table r c b map = mapWithKeys . const . const -- | /O(n)/, assuming the function @r -> c -> a -> b@ takes /O(1)/. -- Map a function over all values in the table. -- -- > mapWithKeys (\r c x -> show r ++ ":" ++ show c ++ ":" ++ x) (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) -- > === fromList [(1,'a',"1:'a':b"),(1,'b',"1:'b':c"),(2,'a',"2:'a':b")] mapWithKeys :: (r -> c -> a -> b) -> Table r c a -> Table r c b mapWithKeys f (Table (rm, cm, sz)) = Table (rm', cm', sz) where rm' = Map.mapWithKey (Map.mapWithKey . f) rm cm' = Map.mapWithKey (Map.mapWithKey . flip f) cm -- | Traverse the (row key, column key, value) triples and collect the results. -- -- > let f r c a = if odd r && c > 'a' then Just (a ++ "x") else Nothing in do -- > traverseWithKeys f (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"b")]) === Nothing -- > traverseWithKeys f (fromList [(1,'b',"b"),(1,'c',"c"),(3,'d',"b")]) === Just (fromList [(1,'b',"bx"),(1,'c',"cx"),(3,'d',"bx")]) traverseWithKeys :: (Applicative t, Ord r, Ord c) => (r -> c -> a -> t b) -> Table r c a -> t (Table r c b) traverseWithKeys f (Table (rm, _, _)) = fromMaps <$> rm' <*> cm' where rm' = Map.traverseWithKey (Map.traverseWithKey . f) rm cm' = transpose' <$> rm' -- | Traverse the (row key, column key, value) triples and collect the 'Just' results. traverseMaybeWithKeys :: (Applicative t, Ord r, Ord c) => (r -> c -> a -> t (Maybe b)) -> Table r c a -> t (Table r c b) traverseMaybeWithKeys f (Table (rm, _, _)) = fromMaps <$> rm' <*> cm' where rm' = Map.traverseWithKey (Map.traverseMaybeWithKey . f) rm cm' = transpose' <$> rm' ------------------------------------------------------------------------------ -- | /O(n)/. Fold the values in the table row by row using the given -- right-associative binary operator. -- -- > Data.Multimap.Table.foldr (:) "" (fromList [(1,'a','b'),(1,'b','c'),(2,'a','d')]) === "bcd" foldr :: (a -> b -> b) -> b -> Table r c a -> b foldr = foldrWithKeys . const . const -- | /O(n)/. Fold the values in the table row by row using the given -- left-associative binary operator. -- -- > Data.Multimap.Table.foldl (flip (:)) "" (fromList [(1,'a','b'),(1,'b','c'),(2,'a','d')]) === "dcb" foldl :: (a -> b -> a) -> a -> Table r c b -> a foldl f = foldlWithKeys (\a _ _ -> f a) -- | /O(n)/. Fold the (row key, column key value) triplets in the table -- row by row using the given right-associative binary operator. -- -- > let f r c a b = show r ++ ":" ++ show c ++ ":" ++ a ++ "|" ++ b in do -- > foldrWithKeys f "" (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === "1:'a':b|1:'b':c|2:'a':d|" foldrWithKeys :: (r -> c -> a -> b -> b) -> b -> Table r c a -> b foldrWithKeys f b (Table (rm, _, _)) = Map.foldrWithKey f' b rm where f' = flip . Map.foldrWithKey . f -- | /O(n)/. Fold the (row key, column key, value) triplets in the table -- row by row using the given left-associative binary operator. -- -- > let f a r c b = show r ++ ":" ++ show c ++ ":" ++ b ++ "|" ++ a in do -- > foldlWithKeys f "" (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === "2:'a':d|1:'b':c|1:'a':b|" foldlWithKeys :: (a -> r -> c -> b -> a) -> a -> Table r c b -> a foldlWithKeys f a (Table (rm, _, _)) = Map.foldlWithKey f' a rm where f' = flip (Map.foldlWithKey . flip f) -- | /O(n)/. A strict version of 'foldr'. Each application of the -- operator is evaluated before using the result in the next application. -- This function is strict in the starting value. -- -- > Data.Multimap.Table.foldr' (:) "" (fromList [(1,'a','b'),(1,'b','c'),(2,'a','d')]) === "bcd" foldr' :: (a -> b -> b) -> b -> Table r c a -> b foldr' = foldrWithKeys' . const . const -- | /O(n)/. A strict version of 'foldl'. Each application of the -- operator is evaluated before using the result in the next application. -- This function is strict in the starting value. -- -- > Data.Multimap.Table.foldl' (flip (:)) "" (fromList [(1,'a','b'),(1,'b','c'),(2,'a','d')]) === "dcb" foldl' :: (a -> b -> a) -> a -> Table r c b -> a foldl' f = foldlWithKeys' (\a _ _ -> f a) -- | /O(n)/. A strict version of 'foldrWithKey'. Each application of the -- operator is evaluated before using the result in the next application. -- This function is strict in the starting value. -- -- > let f r c a b = show r ++ ":" ++ show c ++ ":" ++ a ++ "|" ++ b in do -- > foldrWithKeys' f "" (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === "1:'a':b|1:'b':c|2:'a':d|" foldrWithKeys' :: (r -> c -> a -> b -> b) -> b -> Table r c a -> b foldrWithKeys' f b (Table (rm, _, _)) = Map.foldrWithKey' f' b rm where f' = flip . Map.foldrWithKey' . f -- | /O(n)/. A strict version of 'foldlWithKey'. Each application of the -- operator is evaluated before using the result in the next application. -- This function is strict in the starting value. -- -- > let f a r c b = show r ++ ":" ++ show c ++ ":" ++ b ++ "|" ++ a in do -- > foldlWithKeys' f "" (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === "2:'a':d|1:'b':c|1:'a':b|" foldlWithKeys' :: (a -> r -> c -> b -> a) -> a -> Table r c b -> a foldlWithKeys' f a (Table (rm, _, _)) = Map.foldlWithKey' f' a rm where f' = flip (Map.foldlWithKey' . flip f) -- | /O(n)/. Fold the (row key, column key, value) triplets in the map -- row by row using the given monoid. -- -- > let f r c a = show r ++ ":" ++ show c ++ ":" ++ a ++ "|" in do -- > foldMapWithKeys f (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === "1:'a':b|1:'b':c|2:'a':d|" foldMapWithKeys :: Monoid m => (r -> c -> a -> m) -> Table r c a -> m foldMapWithKeys f (Table (rm, _, _)) = Map.foldMapWithKey f' rm where f' = Map.foldMapWithKey . f ------------------------------------------------------------------------------ -- | /O(r)/. Return a mapping from column keys to values for the given -- row key. -- -- > row 1 (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === Map.fromList [('a',"b"),('b',"c")] -- > row 3 (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === Map.empty row :: Ord r => r -> Table r c a -> Map c a row r (Table (rm, _, _)) = Map.findWithDefault Map.empty r rm -- | /O(c)/. Return a mapping from row keys to values for the given -- column key. -- -- > column 'a' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === Map.fromList [(1,"b"),(2,"d")] -- > column 'c' (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === Map.empty column :: Ord c => c -> Table r c a -> Map r a column c (Table (_, cm, _)) = Map.findWithDefault Map.empty c cm -- | Return a mapping from row keys to maps from column keys to values. -- -- > rowMap (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) -- > === Map.fromList [(1, Map.fromList [('a',"b"),('b',"c")]),(2, Map.fromList [('a',"d")])] rowMap :: Table r c a -> Map r (Map c a) rowMap (Table (rm, _, _)) = rm -- | Return a mapping from column keys to maps from row keys to values. -- -- > columnMap (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) -- > === Map.fromList [('a', Map.fromList [(1,"b"),(2,"d")]),('b', Map.fromList [(1,"c")])] columnMap :: Table r c a -> Map c (Map r a) columnMap (Table (_, cm, _)) = cm -- | Return, in ascending order, the list of all row keys of that have -- at least one value in the table. -- -- > rowKeys (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === [1,2] rowKeys :: Table r c a -> [r] rowKeys (Table (rm, _, _)) = Map.keys rm -- | Return, in ascending order, the list of all column keys of that have -- at least one value in the table. -- -- > columnKeys (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === ['a','b'] columnKeys :: Table r c a -> [c] columnKeys (Table (_, cm, _)) = Map.keys cm -- | Return the set of all row keys of that have at least one value -- in the table. -- -- > rowKeysSet (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === Set.fromList [1,2] rowKeysSet :: Table r c a -> Set r rowKeysSet (Table (rm, _, _)) = Map.keysSet rm -- | Return the set of all column keys of that have at least one value -- in the table. -- -- > columnKeysSet (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === Set.fromList ['a','b'] columnKeysSet :: Table r c a -> Set c columnKeysSet (Table (_, cm, _)) = Map.keysSet cm -- | Convert the table into a list of (row key, column key, value) triples. -- -- > toList (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")] toList :: Table r c a -> [(r, c, a)] toList (Table (rm, _, _)) = Map.toList (Map.toList <$> rm) >>= distr -- | Convert the table into a list of (row key, column key, value) triples -- in ascending order of row keys, and ascending order of column keys -- with a row. -- -- > toRowAscList (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")] toRowAscList :: Table r c a -> [(r, c, a)] toRowAscList (Table (rm, _, _)) = Map.toAscList (Map.toAscList <$> rm) >>= distr -- | Convert the table into a list of (column key, row key, value) triples -- in ascending order of column keys, and ascending order of row keys -- with a column. -- -- > toColumnAscList (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === [('a',1,"b"),('a',2,"d"),('b',1,"c")] toColumnAscList :: Table r c a -> [(c, r, a)] toColumnAscList (Table (_, cm, _)) = Map.toAscList (Map.toAscList <$> cm) >>= distr -- | Convert the table into a list of (row key, column key, value) triples -- in descending order of row keys, and descending order of column keys -- with a row. -- -- > toRowDescList (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === [(2,'a',"d"),(1,'b',"c"),(1,'a',"b")] toRowDescList :: Table r c a -> [(r, c, a)] toRowDescList (Table (rm, _, _)) = Map.toDescList (Map.toDescList <$> rm) >>= distr -- | Convert the table into a list of (column key, row key, value) triples -- in descending order of column keys, and descending order of row keys -- with a column. -- -- > toColumnDescList (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === [('b',1,"c"),('a',2,"d"),('a',1,"b")] toColumnDescList :: Table r c a -> [(c, r, a)] toColumnDescList (Table (_, cm, _)) = Map.toDescList (Map.toDescList <$> cm) >>= distr ------------------------------------------------------------------------------ -- | /O(n)/, assuming the predicate function takes /O(1)/. -- Retain all values that satisfy the predicate. -- -- > Data.Multimap.Table.filter (> "c") (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 2 'a' "d" -- > Data.Multimap.Table.filter (> "d") (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === empty filter :: (a -> Bool) -> Table r c a -> Table r c a filter = filterWithKeys . const . const -- | /O(r)/, assuming the predicate function takes /O(1)/. -- Retain all rows that satisfy the predicate. -- -- > filterRow even (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 2 'a' "d" filterRow :: (r -> Bool) -> Table r c a -> Table r c a filterRow p (Table (rm, cm, _)) = Table (rm', nonEmpty cm', size' rm') where rm' = Map.filterWithKey (const . p) rm cm' = Map.map (Map.filterWithKey (const . p)) cm -- | /O(c)/, assuming the predicate function takes /O(1)/. -- Retain all columns that satisfy the predicate. -- -- > filterColumn (> 'a') (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 1 'b' "c" filterColumn :: (c -> Bool) -> Table r c a -> Table r c a filterColumn p (Table (rm, cm, _)) = Table (nonEmpty rm', cm', size' cm') where rm' = Map.map (Map.filterWithKey (const . p)) rm cm' = Map.filterWithKey (const . p) cm -- | /O(c)/, assuming the predicate function takes /O(1)/. -- Retain all (row key, column key, value) triples that satisfy the predicate. -- -- > filterWithKeys (\r c a -> odd r && c > 'a' && a > "b") (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 1 'b' "c" filterWithKeys :: (r -> c -> a -> Bool) -> Table r c a -> Table r c a filterWithKeys p (Table (rm, cm, _)) = fromMaps rm' cm' where rm' = Map.mapWithKey (Map.filterWithKey . p) rm cm' = Map.mapWithKey (Map.filterWithKey . flip p) cm -- | /O(n)/, assuming the function @a -> 'Maybe' b@ takes /O(1)/. -- Map values and collect the 'Just' results. -- -- > mapMaybe (\a -> if a == "a" then Just "new a" else Nothing) (fromList [(1,'a',"a"),(1,'b',"c"),(2,'b',"a")]) -- > === fromList [(1,'a',"new a"),(2,'b',"new a")] mapMaybe :: (a -> Maybe b) -> Table r c a -> Table r c b mapMaybe = mapMaybeWithKeys . const . const -- | /O(n)/, assuming the function @r -> c -> a -> 'Maybe' b@ takes /O(1)/. -- Map (row key, column key, value) triples and collect the 'Just' results. -- -- > let f r c a = if r == 1 && a == "c" then Just "new c" else Nothing in do -- > mapMaybeWithKeys f (fromList [(1,'a',"b"),(1,'b',"c"),(2,'a',"d")]) === singleton 1 'b' "new c" mapMaybeWithKeys :: (r -> c -> a -> Maybe b) -> Table r c a -> Table r c b mapMaybeWithKeys f (Table (rm, cm, _)) = fromMaps rm' cm' where rm' = Map.mapWithKey (Map.mapMaybeWithKey . f) rm cm' = Map.mapWithKey (Map.mapMaybeWithKey . flip f) cm -- | /O(n)/, assuming the function @a -> 'Either' a1 a2@ takes /O(1)/. -- Map values and separate the 'Left' and 'Right' results. -- -- > mapEither (\a -> if a == "a" then Left a else Right a) (fromList [(1,'a',"a"),(1,'b',"c"),(2,'b',"a")]) -- > === (fromList [(1,'a',"a"),(2,'b',"a")],fromList [(1,'b',"c")]) mapEither :: (a -> Either a1 a2) -> Table r c a -> (Table r c a1, Table r c a2) mapEither = mapEitherWithKeys . const . const -- | /O(n)/, assuming the function @r -> c -> a -> 'Either' a1 a2@ takes /O(1)/. -- Map (row key, column key, value) triples and separate the 'Left' and 'Right' results. -- -- > mapEitherWithKeys (\r c a -> if r == 1 && c == 'a' then Left a else Right a) (fromList [(1,'a',"a"),(1,'b',"c"),(2,'b',"a")]) -- > === (fromList [(1,'a',"a")],fromList [(1,'b',"c"),(2,'b',"a")]) mapEitherWithKeys :: (r -> c -> a -> Either a1 a2) -> Table r c a -> (Table r c a1, Table r c a2) mapEitherWithKeys f (Table (rm, cm, _)) = (fromMaps rm1 cm1, fromMaps rm2 cm2) where (rm1, rm2) = (fmap fst &&& fmap snd) $ Map.mapWithKey (Map.mapEitherWithKey . f) rm (cm1, cm2) = (fmap fst &&& fmap snd) $ Map.mapWithKey (Map.mapEitherWithKey . flip f) cm ------------------------------------------------------------------------------ -- * Non exported functions ------------------------------------------------------------------------------ assoc :: (a, (b, c)) -> (a, b, c) assoc (a, (b, c)) = (a, b, c) distr :: (a, [(b, c)]) -> [(a, b, c)] distr = fmap assoc . uncurry (zip . repeat) -- | Build a table from a row map and a column map. fromMaps :: Map r (Map c a) -> Map c (Map r a) -> Table r c a fromMaps rm cm = Table (rm', cm', size' rm') where rm' = nonEmpty rm cm' = nonEmpty cm fromMaps' :: (Ord r, Ord c) => r -> c -> Map r (Map c a) -> Map c (Map r a) -> Table r c a fromMaps' r c rm cm = Table (rm', cm', size' rm') where rm' = nonEmpty' r rm cm' = nonEmpty' c cm nonEmpty :: Map k1 (Map k2 a) -> Map k1 (Map k2 a) nonEmpty = Map.filter (not . Map.null) nonEmpty' :: Ord k1 => k1 -> Map k1 (Map k2 a) -> Map k1 (Map k2 a) nonEmpty' k1 m = case Map.lookup k1 m of Just m' | Map.null m' -> Map.delete k1 m _ -> m transpose' :: (Ord r, Ord c) => Map r (Map c a) -> Map c (Map r a) transpose' = Map.foldrWithKey' f Map.empty where f r = Map.unionWith Map.union . Map.map (Map.singleton r) size' :: Map k1 (Map k2 a) -> Int size' = sum . fmap Map.size uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d uncurry3 f ~(a, b, c) = f a b c