18
$\begingroup$

For example, I have a nested association like this

<|"fff" -> <|"2001" -> <|5040.` -> {"S20010037", "S20010038", 
       "S20010039", "S20010040", "S20010041", "S20010042"}|>, 
   "2005" -> <|4350.` -> {"S20050448", "S20050449"}, 
     3450.` -> {"S20050998", "S20050999"}|>|>|>

I want to "Flatten" it like this

<|{fff, 2001, 5040.} -> {"S20010037", "S20010038", "S20010039", 
   "S20010040", "S20010041", "S20010042"}, {fff, 2005, 
   4350.} -> {"S20050448", "S20050449"}, {fff, 2005, 
   3450.} -> {"S20050998", "S20050999"}|>

I can't figure out a good way. How to do it elegantly?

$\endgroup$
4

5 Answers 5

20
$\begingroup$

Another idea:

FixedPoint[Association[Normal[#] /. Rule[n_, m_Association] :>
               KeyMap[Append[n, #] &, m]] &, KeyMap[{#} &, asso]]
<|{"fff", "2001", 5040.} -> {"S20010037", "S20010038", "S20010039",
   "S20010040", "S20010041", "S20010042"}, {"fff", "2005", 
   4350.} -> {"S20050448", "S20050449"}, {"fff", "2005", 
   3450.} -> {"S20050998", "S20050999"}|>

Which is the same as:

Association[Normal[KeyMap[List, asso]] //.
 (n_ -> m_Association) :> Normal[KeyMap[Append[n, #] &, m]]]
$\endgroup$
2
  • $\begingroup$ Nice use of FixedPoint. (+1) $\endgroup$
    – Edmund
    Sep 30, 2016 at 10:16
  • $\begingroup$ Clever!! +1 : ) $\endgroup$
    – matheorem
    Oct 8, 2016 at 0:32
8
$\begingroup$

I think this works:

fn[a_ -> _[b__Rule]]  := Flatten[{a, #}] -> #2 & @@@ {b}
fn[x : (_ -> _fn) ..] := Flatten[fn /@ {x}]
fn[a_Association]     := <|a /. Association -> fn|>

Test:

fn[input]   (* input being your input expression *)
<|{"fff", "2001", 5040.} -> {"S20010037", "S20010038", "S20010039", "S20010040", 
   "S20010041", "S20010042"},
  {"fff", "2005", 4350.} -> {"S20050448", "S20050449"},
  {"fff", "2005", 3450.} -> {"S20050998", "S20050999"}|>

Perhaps cleaner:

ClearAll[fn]

a_ -> fn[b__] ^:= Flatten[{a, #}] -> #2 & @@@ Flatten[{b}]
fn[a_Association] := a /. Association -> fn
fn[x_List] := <|x|>

I feel as though there should be a simpler form than this but it eludes me at the moment.

$\endgroup$
7
$\begingroup$
asso = <|"fff" -> <|
    "2001" -> <|
      5040.` -> {"S20010037", "S20010038", "S20010039", "S20010040", 
        "S20010041", "S20010042"}|>, 
    "2005" -> <|4350.` -> {"S20050448", "S20050449"}, 
      3450.` -> {"S20050998", "S20050999"}|>|>|>

Ugly but working:

flatten = Association @* Flatten @* KeyValueMap[
   If[ MatchQ[#2, _Association], 
       flatten @ KeyMap[
          Function[key, If[MatchQ[#, {_, __}], Append[#, key], {#, key}]], 
          #2
       ], 
       # -> #2
   ] &
]

f @ asso
<|
 {"fff", "2001", 5040.} -> {"S20010037", "S20010038", "S20010039",    "S20010040", "S20010041", "S20010042"}, 
 {"fff", "2005", 4350.} -> {"S20050448", "S20050449"}, 
 {"fff", "2005",3450.} -> {"S20050998", "S20050999"}
|>
$\endgroup$
1
  • $\begingroup$ Sorry for late comment. It is Working! Thank you! But for me, Coolwater's answer is easier to understand ; ) $\endgroup$
    – matheorem
    Oct 8, 2016 at 0:40
3
$\begingroup$

You can do this in a different way using the experimental (as of 13.1) Tree structure and related functions. This replaces the somewhat tricky FixedPoint or ReplaceRepeated constructions with an entirely new form of awkwardness.

First, let's name our nested association:

assoc = 
  <|"fff" -> <|
    "2001" -> <|5040.` -> {"S20010037", "S20010038", 
       "S20010039", "S20010040", "S20010041", "S20010042"}|>, 
    "2005" -> <|4350.` -> {"S20050448", "S20050449"}, 
      3450.` -> {"S20050998", "S20050999"}|>|>|>;

Then we can turn it into a Tree object, though it requires a surprising and quasi-documented circumlocution:

tree = ExpressionTree[assoc, "Association"];

From there we can find the "position" of each leaf, meaning the association keys needed to reach it:

pos = TreePosition[tree, _, "Leaves"]
(*
{{Key[fff],Key[2001],Key[5040.]},
 {Key[fff],Key[2005],Key[4350.]},
 {Key[fff],Key[2005],Key[3450.]}} 
*)

I think we need the Key wrappers for our next step, so we'll get rid of them at the end:

KeyMap[ReplaceAll[Key->Identity],
  AssociationMap[
    TreeExtract[tree,#,TreeData]&,
    pos]]
(*
<|
  {fff,2001,5040.}->{S20010037,S20010038,S20010039,S20010040,S20010041,S20010042}, 
  {fff,2005,4350.}->{S20050448,S20050449}, 
  {fff,2005,3450.}->{S20050998,S20050999}
|> 
*)
$\endgroup$
2
  • 1
    $\begingroup$ very interesting! once we have pos we can use it with Extract or Part directly on assoc. E.g., AssociationThread[pos /. Key -> Identity, assoc[[##]] & @@@ pos] or AssociationThread[pos /. Key -> Identity, Extract[assoc, pos]] $\endgroup$
    – kglr
    Jun 7, 2023 at 13:23
  • $\begingroup$ Thank you so much for the new solution : ) But I have to point out that this is not as efficient as FixedPoint solution. $\endgroup$
    – matheorem
    Jun 11, 2023 at 7:15
1
$\begingroup$

A variation on Pillsy's approach using TreeGraph + ExpressionTree:

Interestingly, sink vertices of TreeGraph + ExpressionTree combination contains all the information we need (values and the key-path traversed to reach each value).

tg = TreeGraph[ExpressionTree[assoc, "Association"],
  VertexLabels -> Automatic, VertexLabelStyle -> 14,
  GraphLayout -> {"LayeredDigraphEmbedding", "Orientation" -> Left}]

enter image description here

So we extract and process the leaves to get the desired association:

leaves = GeneralUtilities`GraphSinks @ tg;

AssociationThread @@ Reverse[Transpose@leaves /. Key -> Identity]
<|{"fff", "2001", 5040.} -> {"S20010037", "S20010038", "S20010039", 
  "S20010040", "S20010041", "S20010042"}, 
{"fff", "2005", 4350.} -> {"S20050448", "S20050449"},   
{"fff", "2005", 3450.} -> {"S20050998", "S20050999"}|>
$\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.