yarn-lock-0.2.0: src/Data/MultiKeyedMap.hs
{-# LANGUAGE ExistentialQuantification, NamedFieldPuns, ScopedTypeVariables #-}
{-|
Module : Data.MultiKeyedMap
Description : A multi-keyed map.
Maintainer : Profpatsch
Stability : experimental
-}
module Data.MultiKeyedMap
( MKMap
, at, (!)
, mkMKMap, fromList, toList
, insert
, flattenKeys, keys, values
) where
import qualified Data.Map.Strict as M
import Data.Monoid (All(..))
import qualified Data.List as L
import Data.Proxy (Proxy(..))
import qualified Data.Tuple as T
import qualified Text.Show as Show
-- TODO: add time behaviour of functions to docstrings
-- | A `Map`-like structure where multiple keys can point
-- to the same value, with corresponding abstracted interface.
--
-- Internally, we use two maps connected by an intermediate key.
-- The intermediate key (@ik@) can be anything implementing
-- 'Ord' (for 'Map') and 'Enum' (for 'succ').
data MKMap k v = forall ik. (Ord ik, Enum ik)
=> MKMap
{ keyMap :: M.Map k ik
, highestIk :: ik
, valMap :: M.Map ik v }
-- TODO: is it possible without (Ord k)?
instance (Eq k, Ord k, Eq v) => Eq (MKMap k v) where
-- TODO: not sure if that’s correct, add tests
(==) m1@(MKMap { keyMap = km1
, valMap = vm1 })
m2@(MKMap { keyMap = km2
, valMap = vm2 })
= getAll $ foldMap All
$ let ks1 = M.keys km1 in
-- shortcut if the length of the value map is not equal
[ length vm1 == length vm2
-- the keys have to be equal (the lists are ascending)
, ks1 == M.keys km2 ]
-- now test whether every key leads to the same value
-- I wonder if there is a more efficient way?
++ map (\k -> m1 ! k == m2 ! k) ks1
-- we could test whether the values are equal,
-- but if the implementation is correct they should
-- all be reachable from the keys (TODO: add invariants)
-- TODO: can (/=) be implemented more efficient than not.(==)?
-- | Find value at key. Partial. See 'M.!'.
at :: (Ord k) => MKMap k v -> k -> v
at MKMap{keyMap, valMap} k = valMap M.! (keyMap M.! k)
-- | Operator alias of 'at'.
(!) :: (Ord k) => MKMap k v -> k -> v
(!) = at
{-# INLINABLE (!) #-}
{-# INLINABLE at #-}
infixl 9 !
-- | Create a 'MKMap' given a type for the internally used intermediate key.
mkMKMap :: forall k ik v. (Ord k, Ord ik, Enum ik, Bounded ik)
=> (Proxy ik) -- ^ type of intermediate key
-> MKMap k v -- ^ new map
mkMKMap _ = MKMap mempty (minBound :: ik) mempty
instance (Show k, Show v) => Show (MKMap k v) where
showsPrec d m = Show.showString "fromList " . (showsPrec d $ toList m)
-- | Build a map from a list of key\/value pairs.
fromList :: forall ik k v. (Ord k, Ord ik, Enum ik, Bounded ik)
=> (Proxy ik) -- ^ type of intermediate key
-> [([k], v)] -- ^ list of @(key, value)@
-> MKMap k v -- ^ new map
-- TODO: it’s probably better to implement with M.fromList
fromList p = L.foldl' (\m (ks, v) -> newVal ks v m) (mkMKMap p)
-- | Convert the map to a list of key\/value pairs.
toList :: MKMap k v -> [([k], v)]
toList MKMap{keyMap, valMap} =
map (fmap (valMap M.!) . T.swap) . M.assocs . aggregateIk $ keyMap
where aggregateIk = M.foldlWithKey
(\m k ik -> M.insertWith (++) ik [k] m) mempty
-- | “Unlink” keys that are pointing to the same value.
--
-- Returns a normal map.
flattenKeys :: (Ord k) => MKMap k v -> M.Map k v
flattenKeys MKMap{keyMap, valMap} =
M.foldlWithKey' (\m k ik -> M.insert k (valMap M.! ik) m) mempty keyMap
-- | Return a list of all keys.
keys :: (Ord k) => MKMap k v -> [k]
keys = M.keys . flattenKeys
-- | Return a list of all values.
values :: MKMap k v -> [v]
values (MKMap _ _ valMap) = M.elems valMap
-- TODO: this is like normal insert, it doesn’t search if the value
-- already exists (where it might want to add the key instead).
-- Of course that would be O(n) in the naive implementation.
-- In that case the keyMap should probably be changed to a bimap.
-- also, naming
-- | Equivalent to 'M.insert', if the key doesn’t exist a new
-- singleton key is added.
insert :: (Ord k) => k -> v -> MKMap k v -> MKMap k v
insert k v m@MKMap{keyMap, highestIk, valMap} =
maybe ins upd $ M.lookup k keyMap
where
ins = newVal [k] v m
upd ik = MKMap { keyMap, highestIk, valMap = M.insert ik v valMap }
-- | helper, assumes there is no such value already
--
-- Will leak space otherwise!
newVal :: (Ord k) => [k] -> v -> MKMap k v -> MKMap k v
newVal ks v MKMap{keyMap, highestIk, valMap} =
MKMap { keyMap = L.foldl' (\m k -> M.insert k next m) keyMap ks
, highestIk = next
, valMap = M.insert next v valMap }
where next = succ highestIk