Today I had two maps I needed to combine: consMaps :: Map k a -> Map k b -> Map k (a, b)
. Seeing nothing in Data.Map, I set out to implement this and came up with something unexpectedly ugly:
consMaps :: Map k a -> Map k b -> Map k (a, b)
consMaps ma mb = mapMapMaybe g (Map.unionWith f a' b')
where
a' :: Map k (Maybe (Maybe a, Maybe b))
a' = fmap (\a -> Just (Just a, Nothing)) ma
b' :: Map k (Maybe (Maybe a, Maybe b))
b' = fmap (\b -> Just (Nothing, Just b)) mb
f :: Maybe (Maybe a, Maybe b) -> Maybe (Maybe a, Maybe b) -> Maybe (Maybe a, Maybe b)
f (Just (Just a, _)) (Just (_, Just b)) = Just (Just a, Just b)
f (Just (_, Just b)) (Just (Just a, _)) = Just (Just a, Just b)
-- f (Just a, Just b) _ = Just (a, b) -- impossible in this context
-- f _ (Just a, Just b) = Just (a, b) -- impossible in this context
f _ _ = Nothing
g :: Maybe (Maybe a, Maybe b) -> Maybe (a, b)
g (Just (Just a, Just b)) = Just (a, b)
g _ = Nothing
mapMapMaybe :: (a -> Maybe b) -> Map k a -> Map k b
mapMapMaybe f mp = snd (mapEither (maybe (Left ()) Right . f) mp)
Am I missing something? Is this as good as this gets?
CodePudding user response:
It looks like your consMaps
implementation, with the signature you've given, is just
consMaps :: Map k a -> Map k b -> Map k (a, b)
consMaps = intersectionWith (,)
If instead you wanted a Map k (Maybe a, Maybe b)
, I might write that as
consMaps :: Map k a -> Map k b -> Map k (Maybe a, Maybe b)
consMaps ma mb = unionWith combine ma' mb' where
ma' = fmap (\ a -> (Just a, Nothing)) ma
mb' = fmap (\ b -> (Nothing, Just b)) mb
combine (a, _) (_, b) = (a, b)
CodePudding user response:
If you want Map (a, b)
out then use the other answer (intersectionWith
). If you want a Map (Maybe a, Maybe b)
then that specialized function won't work. Instead, containers
has a merge
function, which covers "basically all" ways to combine Map
s. It takes three strategies: what to do if a key is only in the left map, what to do if a key is only in the right map, and what to do if a key is in both. The strategies are built using helper functions. The idea is that merge
does exactly one traversal of the inputs, so is more efficient than e.g. mapping the inputs and then combining.
import Data.Map.Merge.Lazy
catMaps :: Ord k => Map k a -> Map k b -> Map k (Maybe a, Maybe b)
catMaps = merge left right both
where left = mapMissing $ \_ a -> (Just a, Nothing)
right = mapMissing $ \_ b -> (Nothing, Just b)
both = zipWithMatched $ \_ a b -> (Just a, Just b)
Note that the "right" type for the output in this version is actually Map k (These a b)
, where These
models "inclusive or":
data These a b = This a | That b | These a b
theseMaps :: Ord k => Map k a -> Map k b -> Map k (These a b)
theseMaps = merge left right both
where left = mapMissing $ const This
right = mapMissing $ const That
both = zipWithMatched $ const These