correct way to map a function on entries down the pages in a 3D matrix?

updated:

added another method to the methods I tried below for completion. This is new way of doing it, but most likely not very efficient, did not time it

I can only visualize 3D matrices in my mind as a book made up of pages. Each page is a 2D matrix. The third dimension of the matrix is the number of pages in it. This can be easily carried over to higher dimensions. 4D matrix, is a volume of books, and so on.

Suppose I have a 3D matrix in Mathematica like this:

a = {{1, 2, 3, 4}, {5, 6, 7, 8}};
b = {{9, 10, 11, 12}, {13, 14, 15, 16}};
c = {{17, 18, 19, 20}, {21, 22, 23, 24}};
m = {a, b, c}

(* {  {{1, 2, 3, 4}, {5, 6, 7, 8}},
{{9, 10, 11, 12}, {13, 14, 15,16}},
{{17, 18, 19, 20}, {21, 22, 23, 24}}
}
*)


and want to map f on all elements in each row/col but going down across the pages. From first page to last page of the book, then come back and do the same for next entry and so on. So that the result will be

{
{f[{1, 9, 17}],  f[{2, 10, 18}], f[{3, 11, 19}], f[{4, 12, 20}]},
{f[{5, 13, 21}], f[{6, 14, 22}], f[{7, 15, 23}], f[{8, 16, 24}]}
}


This is explained in this diagram, which I very skillfully just made using Microsoft Visio ;)

There are the two methods I came up with after a bit of struggle. I'd like to find if there is a better or more elegant method. For example, I could not figure how to use Map[f,mat, ??] of the above to produce this result in one step.

Method 1

Transpose[m];
f /@ Transpose[#] & /@ %


Method 2

Map[f[#] &, Table[m[[All, i, j]], {i, 2}, {j, 4}], {2}]


Map[f, Partition[Flatten[m, {2, 3}], 4], {2}]


Summary and conclusions

This problem can be broken up to 2 parts. The first part is how transform the 3D matrix with the correct list in each entry. Once this is done, the rest is easy, since now one can just do Map[f,mat,{2}] on the resulting matrix since this maps f to each entry.

So the trick was is how to do the first part, as the second part I knew. All the answers are very good and learned from them, I selected Leonid's answer since it showed me more general way do this. I did not realized that Transpose can be used as a general transformation using its second arguments. Here is the possible transformations showing the one I wanted. This is useful, since now if I want different transformation, I can look this up, and just use Transpose again with different permutation. I am adding this to my Mathematica sheet cheat.

Appendix

On a side note. The main difference for me when working with Mathematica 3D matrices, is to remember that the first index is the page number and not the row number as the normal case with 2D matrices.

In Matlab, in 3D, the page number is the third index (which is a little bit more natural to me). So in Mathematica, m[[1,2,3]] is the second row, third column in the first page.

While in Matab to reference the same entry, one writes m(2,3,1). Notice the 3rd dimension index location difference. So in Matlab, the first index is always the row and the second index is the column number, regardless if the matrix is 2D or 3D.

Here is the same matrix in Matlab:

a = [1  2  3  4 ;  5 6  7  8];
b = [9  10 11 12; 13 14 15 16];
c = [17 18 19 20; 21 22 23 24];

m = zeros(2,4,3);

m(:,:,1)=a;m(:,:,2)=b;m(:,:,3)=c;

m


gives

m(:,:,1) =
1     2     3     4
5     6     7     8

m(:,:,2) =
9    10    11    12
13    14    15    16

m(:,:,3) =
17    18    19    20
21    22    23    24


It is not a big deal, just something to remember that is all. Ofcourse Matlab does not have a nice to way to use Map's and such so easily as in Mathematica, so it all evens out at the end :)

-
I had not noticed this question, before. I tend to think of 3D matrices as cubes, 4D a list of cubes, 5D a matrix of cubes, etc. –  rcollyer Aug 27 '13 at 19:05
@rcollyer, in matlab land, it is common to talk about 3D matrices as made of pages. Each page is a 2D matrix. It just seems so natural to me to think of it this way. But whatever works for one best is ok :) –  Nasser Aug 27 '13 at 19:09
The one time I used MatLab, it was under duress, so my feelings towards it are a bit skewed. :) But, I agree, whatever works. –  rcollyer Aug 27 '13 at 19:14

Of course, it depends on what one means by "in one step", but here is another alternative:

Map[f, Transpose[m, {3, 1, 2}], {2}]

-
MapThread[f[{##}] &, m, 2]

-
Be aware that this method is inefficient on packed arrays, which incidentally came up here. –  Mr.Wizard Aug 6 '13 at 7:24
@Mr.Wizard ,Thank you. –  chyaong Aug 6 '13 at 14:14

Just an alternative method:

Array[f @ m[[ ;; , ##]] &, {2, 4}]

-

A very similar question just came up, see here: Combine two matrices. Unaware of this thread here, I offered another approach to the problem and was encouraged to add it here, too (now that it wasn't closed as duplicate).

Pure & Listable Function

Using a pure function that is Listable, we can avoid declaring f as Listable (see below). Thus, based on the answer linked above and also on @rm -rf's excellent answer here, we can write:

Function[, f[{##}], Listable][Sequence @@ m]


The syntax highlighting won't really like it...

Some Background: Listable

The idea is based on defining f (or here: f1) as Listable:

SetAttributes[f1,Listable]


and then simply (for explicit 3 "pages"):

f1[a,b,c]


or for your m:

f1[Sequence@@m]


Note, that the output is slightly different from what you desire (...f1[1,9,17]... instead of ...f1[{1,9,17}]...). For that, you could e.g.

f1[Sequence@@m]/.f1->Composition[f,List]


Now we want to reduce side effects of declaring stuff as Listable. My preferred approach is shown at the beginning, i.e. using a pure & listable function.

Also, you could consider wrapping all in Block (thanks to @rcollyer for pointing this out):

Block[{f}, SetAttributes[f, Listable]; f[Sequence @@ m]]


respectively:

Block[{ff}, SetAttributes[ff, Listable]; ff[Sequence @@ m]/.ff->Composition[f,List]]

-

As I commented under chyanog's answer (which I also voted for) his method of using MapThread unpacks packed arrays at a significant performance penalty. The same is true for Pinguin Dirk's Listable method. I would like to provide some timings illustrating this consideration.

I chose Tr as a fast function for a generic example. I will use timeAvg which has been posted many times.

f = Tr;
times =
Table[
m = RandomInteger[99, n {3, 2, 4}];
{Map[f, Transpose[m, {3, 1, 2}], {2}] // timeAvg,
MapThread[f[{##}] &, m, 2] // timeAvg,
Function[, f[{##}], Listable][Sequence @@ m] // timeAvg},
{n, 10, 150, 10}
];

ListLogPlot[times\[Transpose], Joined -> True]

TableForm[times,