Key-dependent maps preserving association

Suppose I have some association a0 = <| key1 -> val1, ... |> and some function f[key, val]. I'd like to produce the association a1 = <| key1 -> f[key1, val1], key2 -> f[key2, val2], ... |>.

I can see several methods to do this, each of which feels very kludgy in certain ways. In increasing order of how much they make me feel uneasy, here they are:

Way 1: Forcing the keys in with AssociationMap, then just mapping.

RuleWithKey[Rule[key_,val_]] := Rule[key, {key, val}];
a1 = Map[f @@ #, AssociationMap[RuleWithKey, a0]].


(Related question: why does a1 = Map[f@@#, AssociationMap[Function[ Rule[key,val], Rule[key,{key,val}] ],a0]]; not work? I get an error "Function: Parameter specification key->val in Function[key->val,key->{key,val}] should be a symbol or a list of symbols." but I can define RuleWithKey just fine. Presumably this is something to do with Rule getting evaluated in one context but not in the other?)

Way 2: Deconstructing with Keys and KeyValueMap, then reconstructing with AssociationThread.

a1 = AssociationThread[Keys[a0], KeyValueMap[f, a0]];


Way 3: Creating a key-association with Identity, then using Merge.

a1 = Merge[ AssociationMap[Identity, Keys[a0]], a0, f@@# ];


(We can assume that we know that key /= val always holds; there are even-more-kludgy ways to get around this, of course. Despite this assumption, this implementation particularly scares me, which is never a good thing to say about a piece of code.)

Way 4: Using Lookup operations in an AssociationMap.

a1 = AssociationMap[ f[#, a0[#]]&, a0 ];


Can you come up with a better method (by the admittedly difficult-to-interpret standard of "feeling not kludgy to me")?

I'm primarily interested in performant code (say associations with $$\approx 10^6$$ keys), so deconstructing and rebuilding associations isn't something that I'm super-thrilled about. I also don't understand what operations on associations are efficient or inefficient, so comments or answers that discuss why some of these implementations are fast or slow (relatively speaking) would also be helpful.

• What about the following variation of your "Way 1": AssociationMap[#[[1]] -> (f @@ #) &, <|a -> 1, b -> 2, c -> 3, d -> 4|>]? Sep 23 '20 at 1:13
• @J.M., that's slightly more elegant, true. But my understanding is that none of the four solutions are as performant as I'd like (primarily because none of them can "guarantee" to the backend that the keys are still distinct and in the same order, in the same way that a Map can); do you know of any solution that might ensure this? Sep 23 '20 at 1:43

1 Answer

This is not a complete answer as I have nothing to offer on the question of performance, but another way to obtain the result is to use MapIndexed:

a0 = <| "key1" -> "val1", "key2" -> "val2", "key3" -> "val3" |>;

MapIndexed[f[#2[[1, 1]], #] &, a0]

(* <| key1 -> f[key1,val1], key2 -> f[key2,val2], key3 -> f[key3,val3] |> *)