7
$\begingroup$

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

$\endgroup$

3 Answers 3

7
$\begingroup$

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 *)
$\endgroup$
5
$\begingroup$

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"]|> *)
$\endgroup$
2
$\begingroup$

Consider MapAt:

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

enter image description here

$\endgroup$
1
  • $\begingroup$ 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? $\endgroup$ Commented Dec 29, 2017 at 17:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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