27

Consider an example Association:

assoc = <|"a" -> 1, "b" -> "x", "this_key_is_too_long_to_type" -> {1}|>;

Suppose I want to replace "this_key_is_too_long_to_type" with "c". I can replace it by transforming into Normal land and back into Association land:

Association[
  ReplaceAll[Normal[assoc], Rule["this_key_is_too_long_to_type", rhs_] :> Rule["c", rhs]]]

But I have found with Associations that there is usually a compact and efficient way and that this double-transformation is usually a signal that I'm not using it. What's the best way to rename a key?

xyz
  • 605
  • 4
  • 38
  • 117
ArgentoSapiens
  • 7,780
  • 1
  • 32
  • 49
  • 1
    Based on the complexity of the answers, I'm going to wager that "... there is usually a compact and efficient way..." is false in this case. – bobthechemist Nov 03 '14 at 22:42
  • 3
    @bobthechemist, if that's the case, let's use this as an opportunity to request ReplaceKey or something in a future version. – ArgentoSapiens Nov 04 '14 at 00:50
  • ...or at least a more general KeyMapAt or AssociationMapAt – Rojo Nov 05 '14 at 11:38
  • What property does an Association have that implies that ReplaceAll cannot replace keys? What is the general rule at play here? – Alan Jul 14 '17 at 21:35

5 Answers5

19

Somewhat similar to the answer of evanb, but without explicit mutations:

keyRename[a_, old_ -> new_] /; KeyExistsQ[a, old] := KeyDrop[old]@Append[a, new -> a[old]]

So that

keyRename[assoc, "this_key_is_too_long_to_type" -> "c"]

(* <|"a" -> 1, "b" -> "x", "c" -> {1}|> *)

It should be noted that this solution doesn't preserve the order of the keys. A variant that does is:

keyRename[a_, key_ -> key_] := a
keyRename[a_, old_ -> new_] /; KeyExistsQ[a, old] := 
        KeyDrop[old]@Insert[asc, new -> asc[old], Key[old]]
Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Hi, Leonid. Your method seems only works for one key. What if I have a bunch to replace. I can't understand why association can not use simply /. For example, assoc/.{c1->d1,c2->d2,...} to replace many keys in a single step. I encountered this kind of occasion many times, quite annoying. – matheorem Nov 02 '15 at 01:17
  • @matheorem I think that massive key renaming is not such a common (frequently needed) operation. Using ReplaceAll will be ambiguous - do you want to replace keys or values. Since KeyDrop and Append are constant-time for associations, you can in principle use my keyRename with Fold: Fold[keyRename, assoc, replacements]. Or, if speed is important, do something more clever, all at once, that would be a few times faster. – Leonid Shifrin Nov 02 '15 at 13:45
  • 2
    @LeonidShifrin, even shorter, along the lines of ArgentoSapiens's O(n): keyReplace[rule_] := KeyMap[Replace[rule]]; keyReplace[{rules__}] := KeyMap[Replace[{rules}]]; /// you'd be surprised how often it's necessary to replace keys programmatically - namespace normalization from multiple sources. – alancalvitti May 07 '17 at 17:20
  • This will modify the order of the keys though. I cannot come up with a solution that is not O(n) and it truly will not change anything else in the association than the key name. Of course, one has to consider the case when the renaming will cause a key collision. Still, it seems like there should be a simple solution ... – Szabolcs Mar 19 '20 at 11:39
  • Maybe keyRename[asc_?AssociationQ, old_ -> new_] := KeyDrop[old]@Insert[asc, new -> asc[old], Key[old]]? – Szabolcs Mar 19 '20 at 11:42
  • @Szabolcs That should work, except you'd have to throw in a check that old and new are different, and have a no-op otherwise (your suggestion would in this case remove the key). Could just add a separate clause like keyRename[asc_?AssociationQ, key_ -> key_] := asc. – Leonid Shifrin Mar 19 '20 at 12:09
  • 1
    @Szabolcs Feel free to edit your solution into the post. – Leonid Shifrin Mar 19 '20 at 12:11
  • @Szabolcs Thanks. I have added to your code a clause for trivial renaming case, otherwise for that case the function would still have a bug. – Leonid Shifrin Mar 19 '20 at 13:44
16

There is, of course:

assoc = <|"a" -> 1, "b" -> "x", "this_key_is_too_long_to_type" -> {1}|>;
assoc["c"] = assoc["this_key_is_too_long_to_type"];
assoc["this_key_is_too_long_to_type"] =.
assoc
(* <|"a" -> 1, "b" -> "x", "c" -> {1}|> *)

Not sure if there's an elegant way to do it in one step.

evanb
  • 6,026
  • 18
  • 30
7

Here's a way to do it in one line without using Normal first.

KeyMap[If[SameQ[#, "this_key_is_too_long_to_type"], "c", #] &, assoc]

Or:

KeyMap[# /. "this_key_is_too_long_to_type" -> "c" &, assoc]
ArgentoSapiens
  • 7,780
  • 1
  • 32
  • 49
4

I normally use the operator

KeyMap[Replace["this_key_is_too_long_to_type" -> "c"]]

and apply it to the association. It generalizes easily to renaming multiple keys:

KeyMap[Replace[{"a" -> "x", "b" -> "y"}]]

This is simple and convenient, but it will have to check every key, which is not optimal if you only want to rename one. In that case, use Leonid's solution.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
2

Late in the game. Here is a generalization of @ArgentoSapiens, encapsulated in a function and working for lists of Associations.

keyRename // ClearAll
keyRename[a:{_Association...}, rule_Rule]:= keyRename[a, {rule}]
keyRename[a_Association, rule_Rule]:= keyRename[a, {rule}]
keyRename[a_Association, rule:{_Rule...}]:= KeyMap[#/.rule &, a]
keyRename[a:{_Association...}, rule:{_Rule...}]:= keyRename[#,rule]&/@a

Some tests:

a = {<|"a"-> 1, "b"-> 2|>, <|"a"-> 3, "b"-> 4|>};
keyRename[a,{"a" -> "a2", "b" -> "b2"}]
keyRename[a,"a" -> "a2"]
keyRename[a[[1]],"a"-> "a2"]

we get

{<|"a2" -> 1, "b2" -> 2|>, <|"a2" -> 3, "b2" -> 4|>}
{<|"a2" -> 1, "b" -> 2|>, <|"a2" -> 3, "b" -> 4|>}
<|"a2" -> 1, "b" -> 2|>
Murta
  • 26,275
  • 6
  • 76
  • 166