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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.