I think it’s easier to understand Flatten in terms of Dimension and Part. Let’s start with an arbitrary table, but formatted slightly to help keep track of things.
In[]:=
orig=Table[StringRiffle[{a,b,c,d},"."],{a,1,2},{b,1,3},{c,1,4},{d,1,5}];orig//TableForm
Out[]//TableForm=
We have the expected dimensions:
In[]:=
Dimensions@orig
Out[]=
{2,3,4,5}
Let’s start with a fairly simple Flatten.
In[]:=
flatA=Flatten[orig,{{4}}];flatA//TableForm(*Thisformisunambiguous,soitcouldhavebeensimplifiedtoFlatten[orig,{4}]*)
Out[]//TableForm=
|
| ||||||||||||||||||||||||
|
| ||||||||||||||||||||||||
|
| ||||||||||||||||||||||||
|
| ||||||||||||||||||||||||
|
|
What happened? Per the documentation, we did a sort of transpose where level 4 became level 1. One consequence is that our dimensions changed.
In[]:=
Dimensions@flatA
Out[]=
{5,2,3,4}
We can see that the size of the previously fourth level now applies to the first level, and the others have been pushed back. Next, let’s try to find where each element went using Part specs.
In[]:=
orig[[1,2,3,4]]==flatA[[4,1,2,3]]
Out[]=
True
We can do more tests if we want. Select a random position and compare what’s in that position in orig with what’s in flatA at the appropriately permuted position.
In[]:=
With[{position=RandomInteger/@{{1,2},{1,3},{1,4},{1,5}}},{Extract[orig,position],Extract[flatA,Permute[position,{2,3,4,1}]]}]
Out[]=
{1.2.2.3,1.2.2.3}
That Permute might look like magic, but we’ll discuss it more later. Just slightly trickier is flattening with two levels independently.
In[]:=
flatB=Flatten[orig,{{2},{3}}];flatB//TableForm
Out[]//TableForm=
Check the dimensions:
In[]:=
Dimensions@flatB
Out[]=
{3,4,2,5}
And again, we can easily determine the necessary permutation of the Part spec
In[]:=
With[{position=RandomInteger/@{{1,2},{1,3},{1,4},{1,5}},permutation={3,1,2,4}},Print[position];Print[Permute[position,permutation]];{Extract[orig,position],Extract[flatB,Permute[position,permutation]]}]
{2,1,1,3}
{1,1,2,3}
Out[]=
{2.1.1.3,2.1.1.3}
Okay, to summarize this simple examples, if you start with a list with n levels and dimension {d1,d2,d3,...,dn}, then if you do this
Flatten[list,{{x},{y}}]
You’ll end up with a new list of dimension {dx,dy,...} where everything after the second position is just the dimensions in order with the xth and yth ones removed. Furthermore, the element at position {p1,p2,...pn} in list can now be found at position {px,py,...} with the same constraint for the rest of the list that we used for dimension.
A cleaner way to understand this is by using permutations as I did for the examples above. Given a list, a permutation of it can be specified as just another list, where the value at each index tells you the “destination index”. So, for example,
In[]:=
Permute[{a,b,c,d},{3,1,2,4}]
Out[]=
{b,c,a,d}
Index 1 in {3,1,2,4} is 3, so a goes to the 3rd position. Index 2 in {3,1,2,4} is 1, so b goes to the 1st position. And so forth.
Moving on to even trickier, let’s look at this:
In[]:=
flatC=Flatten[orig,{{2,3}}];flatC//TableForm(*Again,wehaveanunambiguouscase,sothiscouldhavebeendonewithFlatten[orig,{2,3}]*)
Out[]//TableForm=
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
| ||||||||||
|
|
Now we have a different depth, but looking at the dimensions suggests how the “merging” happened:
In[]:=
Dimensions@flatC
Out[]=
{12,2,5}
It turns out that we just flattened the top 2 levels together. I.e.
In[]:=
Flatten[orig,{{2,3}}]==Flatten[Flatten[orig,{{2},{3}}],1]
Out[]=
True
Or using the variables we’ve already defined,
In[]:=
flatC==Flatten[flatB,1]
Out[]=
True
Now, how can we trace between orig and flatC? For example, we have
In[]:=
orig[[1,2,3,4]]==flatC[[7,1,4]]
Out[]=
True
Where did that 7 come from? Remember the dimensions of flatB were {3,4,2,5}, and also remember that we found our element in flatB using a permutation of {3,2,1,4}, which took {1,2,3,4} to {2,3,1,4}. Okay, so we’re looking for where {2,3,1,4} went after we flattened. Just looking at the top two levels, the part {2,3} will be in the second top-level list at the 3rd position. Each top-level list has length 4, so we “passed over” the 4 elements of the first top-level list and took the third element of the second top-level list. That’s element at position 7 after we flatten.
Okay, to summarize, Flatten can be understood as follows:
* the new dimensions are found by applying a permutation to the original dimensions (and then taking products if some levels are merged)
* elements in the resulting list can be found by applying that same permutation to the part spec for where the element was found in the original list
* if we’re using the form of flatten where levels are merged, that’s just as if we did the non-merging case and applied an additional Flatten at the appropriate level
* the new dimensions are found by applying a permutation to the original dimensions (and then taking products if some levels are merged)
* elements in the resulting list can be found by applying that same permutation to the part spec for where the element was found in the original list
* if we’re using the form of flatten where levels are merged, that’s just as if we did the non-merging case and applied an additional Flatten at the appropriate level