27
$\begingroup$

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?

| improve this question | | | | |
$\endgroup$
  • 1
    $\begingroup$ 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. $\endgroup$ – bobthechemist Nov 3 '14 at 22:42
  • 3
    $\begingroup$ @bobthechemist, if that's the case, let's use this as an opportunity to request ReplaceKey or something in a future version. $\endgroup$ – ArgentoSapiens Nov 4 '14 at 0:50
  • $\begingroup$ ...or at least a more general KeyMapAt or AssociationMapAt $\endgroup$ – Rojo Nov 5 '14 at 11:38
  • $\begingroup$ What property does an Association have that implies that ReplaceAll cannot replace keys? What is the general rule at play here? $\endgroup$ – Alan Jul 14 '17 at 21:35
16
$\begingroup$

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.

| improve this answer | | | | |
$\endgroup$
17
$\begingroup$

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]]
| improve this answer | | | | |
$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – matheorem Nov 2 '15 at 1:17
  • $\begingroup$ @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. $\endgroup$ – Leonid Shifrin Nov 2 '15 at 13:45
  • 2
    $\begingroup$ @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. $\endgroup$ – alancalvitti May 7 '17 at 17:20
  • $\begingroup$ 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 ... $\endgroup$ – Szabolcs Mar 19 at 11:39
  • 1
    $\begingroup$ @Szabolcs Feel free to edit your solution into the post. $\endgroup$ – Leonid Shifrin Mar 19 at 12:11
7
$\begingroup$

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]
| improve this answer | | | | |
$\endgroup$
  • 3
    $\begingroup$ The problem with this code is that it is O(n), where n is the number of keys, while it should better be O(1). $\endgroup$ – Leonid Shifrin Nov 3 '14 at 22:07
3
$\begingroup$

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.

| improve this answer | | | | |
$\endgroup$
1
$\begingroup$

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|>
| improve this answer | | | | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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