Skip to main content

gathering - How to organically merge nested associations?



Suppose I want to construct an association of associations, such as a list of people with attributes:


peopleFacts=<| alice-> <|age->29,shoeSize->7|>, bob-> <|age->27,sex->male|> |>

However, I want to grow and update this organically by adding facts as I learn them.


peopleFacts[["steve","hairColor"]] = "red";
peopleFacts[["bob","age"]] = "22";
peopleFacts[["steve","major"]] = "physics";

It's possible to accomplish this awkwardly by either (a) filling the database with blank entries or (b) laboriously checking at each level of association to see if an entry is blank before filling it in (except the last level, where AssociateTo helps you). But I think there must be a more elegant way. Here is what I've tried.


This method breaks because it tosses out the second key:



 In[]:= peopleFacts[["steve","hairColor"]] = "red";
peopleFacts

Out[]:= <|steve -> red, alice-> <|age->29,shoeSize->7|>, bob-> <|age->27,sex->male|> |>

This method drops existing data:


 In[]:= peopleFacts

Out[]:= <| alice-> <|age->29,shoeSize->7|>, bob-> <|age->27,sex->male|> |>


In[]:= AssociateTo[peopleFacts, alice-> <|"sport"->"baseball"|>;
peopleFacts

Out[]:= <| alice-> <|sport->baseball|>, bob-> <|age->27,sex->male|> |>

This method just doesn't evaluate:


 In[]:= AssociateTo[peopleFacts[["chris"]], "favoriteFood" -> "sushi"]

Out[]:= AssociateTo[peopleFacts[["chris"]], "favoriteFood" -> "sushi"]


EDIT: Here is a way-too-awkward method adapted from this answer by SuTron.


 In[]:= peopleFacts

Out[]:= <| alice-> <|age->29,shoeSize->7|>, bob-> <|age->27,sex->male|> |>

In[]:= Module[{temp = peopleFacts["alice"]},
AssociateTo[temp, "sport"->"baseball"];
AssociateTo[peopleFacts, "alice" -> temp];
];
peopleFacts


Out[]:= <| alice-> <|age->29,shoeSize->7,sport->baseball|>, bob-> <|age->27,sex->male|> |>

It's not hard to imagine defining a custom update function like


  NestedAssociateTo[peopleFacts,{"steve","haircolor","red"}]

that would handle this all for you, but I'd much rather have a nice native Mathematica solution that is optimized, and that I don't have to maintain or worry about.



Answer



Initial data:


peopleFacts = <|

alice -> <|age -> 29, shoeSize -> 7|>,
bob -> <|age -> 27, sex -> male, hair -> <|Color -> RGBColor[1, 0, 0]|>
|>
|>;



Here is a version of RecurAssocMerge reduced to a single definition.


MergeNested = If[MatchQ[#, {__Association}], Merge[#, #0], Last[#]] &

MergeNested @ {peopleFacts, <|bob -> <|hair -> <|length -> 120|>|>|>}



 <|
alice -> <|
age -> 29,
shoeSize -> 7|>,
bob -> <|
age -> 27,
sex -> male,
hair -> <|Color -> RGBColor[1, 0, 0], length -> 120|>

|>
|>



Special case of 2-level deep association


Merge[{
peopleFacts,
<|bob -> <|hairColor -> 1|>|>
},
Association

]

"Tidy" approach to write NestedMerge:


RecurAssocMerge[a : {__Association}] := Merge[a, RecurAssocMerge];
RecurAssocMerge[a_] := Last[a];



  • adding key to deep level association:


    RecurAssocMerge[

    {peopleFacts, <|bob -> <|hair -> <|length -> 120|>|>|>}
    ]


     <|alice -> <|age -> 29, shoeSize -> 7|>, 
    bob -> <|age -> 27, sex -> male, hair -> <|
    Color -> RGBColor[1, 0, 0], length -> 120 |>
    |>
    |>




  • entirely new tree


    RecurAssocMerge[
    {peopleFacts, <|kuba -> <|hair -> <|length -> 120|>|>|>}
    ]


     <|
    alice -> <|age -> 29, shoeSize -> 7|>,

    bob -> <|age -> 27, sex -> male, hair -> <|Color -> RGBColor[1, 0, 0]|>
    |>,
    kuba -> <|hair -> <|length -> 120|>|>
    |>





Section added by Jess Riedel:


Specialize to single new entry



RecurAssocMerge defined above is a general method for merging nested Associations. We can define an abbreviation for the special case when we are adding only a single new entry.


RecurAssocMerge[ini_Association, path_List, value_] := RecurAssocMerge[{
ini, Fold[<|#2 -> #|> &, value, Reverse@path]
}]

Then we can just do


RecurAssocMerge[peopleFacts, {bob, hair, length}, 120]


 <|alice -> <|age -> 29, shoeSize -> 7|>, 

bob -> <|age -> 27, sex -> male, hair -> <|
Color -> RGBColor[1, 0, 0], length -> 120 |>
|>
|>

Notes


If you want to modify peopleFacts the peopleFacts = Merge... is needed of course.


Comments

Popular posts from this blog

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...

equation solving - Invert and fit implicitly defined curve

I need to fit an implicitly defined curve. I thought I could get some data out of Solve , and then using FindFit . Therefore, I would like to find the relation the parametric curve defined by $F(x,y)=0$: Solve[-(1/2) + 1/2 (0.41202 BesselK[0, 0.1 Sqrt[x^2 + y^2]] + (0.101483 x BesselK[1, 0.1 Sqrt[x^2 + y^2]])/Sqrt[x^2 + y^2]) == 0, y] But I can't get an output: Solve was unable to solve the system with inexact coefficients or the system obtained by direct rationalization of inexact numbers present in the system. Since many of the methods used by Solve require exact input, providing Solve with an exact version of the system may help. >> Edit: In particular, I would like to fit the data coming from the curve with the expression of another curve, and not with a function $f(x)$. In particular, since this clearly looks like a cardioid , I would like it to fit to something like it. What other strategies could I try?

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...