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]]

enter image description here

  • 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

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.