18

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?

matheorem
  • 17,132
  • 8
  • 45
  • 115
  • 2
    Related: http://mathematica.stackexchange.com/q/55745/121, http://mathematica.stackexchange.com/q/83507/121, http://mathematica.stackexchange.com/q/86578/121, http://mathematica.stackexchange.com/q/107399/121 – Mr.Wizard Sep 30 '16 at 10:00
  • 1
    See also associationFlatten function described here: http://community.wolfram.com/groups/-/m/t/837061?p_p_auth=QuUJXH8q – alancalvitti Sep 30 '16 at 12:21
  • @Mr.Wizard Sorry for late comment, these are so useful! Thank you so much! – matheorem Oct 08 '16 at 00:12
  • @matheorem No problem! I am glad you found them relevant. – Mr.Wizard Oct 08 '16 at 00:13

5 Answers5

20

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]]]
Coolwater
  • 20,257
  • 3
  • 35
  • 64
8

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.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
7
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"}
|>
Kuba
  • 136,707
  • 13
  • 279
  • 740
  • Sorry for late comment. It is Working! Thank you! But for me, Coolwater's answer is easier to understand ; ) – matheorem Oct 08 '16 at 00:40
3

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}
|> 
*)
Pillsy
  • 18,498
  • 2
  • 46
  • 92
  • 1
    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]] – kglr Jun 07 '23 at 13:23
  • Thank you so much for the new solution : ) But I have to point out that this is not as efficient as FixedPoint solution. – matheorem Jun 11 '23 at 07:15
1

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"}|>
kglr
  • 394,356
  • 18
  • 477
  • 896