# How to "flatten" a nested Association?

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?

• Sep 30, 2016 at 10:00
• See also associationFlatten function described here: community.wolfram.com/groups/-/m/t/837061?p_p_auth=QuUJXH8q Sep 30, 2016 at 12:21
• @Mr.Wizard Sorry for late comment, these are so useful! Thank you so much! Oct 8, 2016 at 0:12
• @matheorem No problem! I am glad you found them relevant. Oct 8, 2016 at 0:13

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]]]

• Nice use of FixedPoint. (+1) Sep 30, 2016 at 10:16
• Clever!! +1 : ) Oct 8, 2016 at 0:32

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.

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"}
|>

• Sorry for late comment. It is Working! Thank you! But for me, Coolwater's answer is easier to understand ; ) Oct 8, 2016 at 0:40

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}
|>
*)

• 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 7, 2023 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. Jun 11, 2023 at 7:15

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}]


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

leaves = GeneralUtilitiesGraphSinks @ 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"}|>