28
$\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?

$\endgroup$
4
  • 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$ Commented Nov 3, 2014 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$ Commented Nov 4, 2014 at 0:50
  • $\begingroup$ ...or at least a more general KeyMapAt or AssociationMapAt $\endgroup$
    – Rojo
    Commented Nov 5, 2014 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
    Commented Jul 14, 2017 at 21:35

5 Answers 5

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.

$\endgroup$
19
$\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]]
$\endgroup$
8
  • $\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
    Commented Nov 2, 2015 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$ Commented Nov 2, 2015 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$ Commented May 7, 2017 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
    Commented Mar 19, 2020 at 11:39
  • 1
    $\begingroup$ @Szabolcs Feel free to edit your solution into the post. $\endgroup$ Commented Mar 19, 2020 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]
$\endgroup$
1
  • 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$ Commented Nov 3, 2014 at 22:07
5
$\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.

$\endgroup$
2
$\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|>
$\endgroup$

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.