Reputation: 7930
Let's say I have two HashMaps:
import Data.HashMap.Strict
m1 :: HashMap Char Int
m2 :: HashMap Char Int
??? :: (v1 -> v2 -> v3) -> HashMap k v1 -> HashMap k v2 -> HashMap k v3
-- or even
??? :: (k -> v1 -> v2 -> v3) -> HashMap k v1 -> HashMap k v2 -> HashMap k v3
I'd like some kind of zip
to walk over these maps, where:
This matches the signature of intersectionWithKey
with semantics closer to unionWithKey
.
A concrete example: for a simple case of applying (,)
with a 0 default, I can do
unionWith add (map (,0) m1) (map (0,) m2) :: HashMap Char (Int, Int)
add (a, x) (b, y) = (a+b, x+y)
This provokes the question: is there a clean way to do this in general?
Upvotes: 2
Views: 290
Reputation: 531958
In general:
lookupDefault
to
get the appropriate value (default or real) from each map in order
to compute the value for the new mapzipMap :: (v1 -> v2 -> v3) -- Combining function
-> v1 -- default value for keys unique to the second map
-> v2 -- default value for keys unique to the first map
-> HashMap k v1
-> HashMap k v2
-> HashMap k v3
zipMap f d1 d2 m1 m2 = let allKeys = keys (union m1 m2)
f' k = (k, f (lookupDefault d1 k m1) (lookupDefault d2 k m2))
in fromList $ map f' allKeys
Upvotes: 1
Reputation: 27766
The these package provides the These
datatype, which is a bit like Either
but has an extra constructor for when both cases are present.
The package also defines typeclasses for "zipping with padding" over data structures. In particular the Align
typeclass, which has a HashMap
instance.
For example, to zip two maps using 0
s for missing entries:
import Data.These
import Data.Align
padWithZeros :: (Align f, Num a, Num b) => f a -> f b -> f (a, b)
padWithZeros = alignWith (fromThese 0 0)
There are also Data.Align.Indexed
and Data.Align.Keyed
, which give the zipping function access to the key.
One disadvantage of these is that the dependency foot print is somewhat heavy.
Upvotes: 1