# Applying a function by Key pattern, preserving Association order

Any convenient ways to apply a function to Keys that match a pattern in an Association? Method should handle:

1. Mixture of String, Symbol and composite Keys
2. Preserve the input Association order
3. Be efficient

Off the bat, KeyValuePattern only matches a single instance per Association, so normalizing an Association into singleton Associations is not likely to satisfy (3)

Illustrating current gap in functionality rather than a path to solution (Dataset is not strictly necessary but helps abbreviate Queries)

ds =  <|"b2" -> 2, "a1" -> 1, c3 -> 3|> // Dataset


Note c3 is a Symbol - Suppose the pattern is:

keyPattern = k_String /; StringMatchQ[k, "a*"]


This tentative query:

keyPatternQuery[patt_ -> f_][as_Association] :=
Query[{KeySelect[MatchQ[patt]] /* Query[All, f],
KeySelect[MatchQ[patt ] /* Not]} /* Apply[Join]][as];


Fails (2) or would require resorting by input keys, likely failing (3)

ds[keyPatternQuery[keyPattern -> f]] // Normal


<|"a1" -> f[1], "b2" -> 2, c3 -> 3|>

This should do what you want:

ClearAll[KeyPatternQuery];
KeyPatternQuery[pat_ -> f_][asc_] := With[{
g = Function[{key, val}, If[MatchQ[key, pat], key -> f[val], key -> val]]
},
Association@@KeyValueMap[g, asc]
]


For example with

asc = <|"b2" -> 2, "a1" -> 1, c3 -> 3|>


and

keyPattern = k_String /; StringMatchQ[k, "a*"];


we get

KeyPatternQuery[keyPattern -> f][asc]
(* <|"b2" -> 2, "a1" -> f[1], c3 -> 3|> *)


or, alternatively

Dataset[asc][KeyPatternQuery[keyPattern -> f]] // Normal
(* <|"b2" -> 2, "a1" -> f[1], c3 -> 3|> *)


Performance seems to be ok, too:

(* generate an association with 1000 entries *)
rasc = Association@@Table[
RandomChoice[{ToExpression, Identity}]@RandomWord[] -> RandomInteger[{0, 100}],
10^3
];

First@RepeatedTiming[KeyPatternQuery[keyPattern -> f][rasc]]

(* 0.0074 *)


Here is a way that uses MapIndexed:

keyPatternQuery[patt_ -> f_] := MapIndexed[If[MatchQ[#2, {Key[patt]}], f[#], #]&]


So then:

ds[keyPatternQuery[keyPattern -> f]] // Normal

(* <|"a1" -> f[1], "b2" -> 2, c3 -> 3|> *)


or:

<|{1, "x"} -> "a", {2, "y"} -> "b", {3, "y"} -> "c"|> // keyPatternQuery[{_, "y"} -> f]

(* <|{1, "x"} -> "a", {2, "y"} -> f["b"], {3, "y"} -> f["c"]|> *)


Consider MapAt:

MapAt[f, ds, Position[Keys@ds, keyPattern]]


• This works well enough as is, but is it possible to modify it so that patterns can be used on the right hand side of Rule (or rather RuleDelayed) similar to Case? – alancalvitti Dec 29 '17 at 17:50