14
$\begingroup$

Many experienced users on this site tend to use Map (and its variants, MapAt, MapIndexed, etc.) rather than Table. When applying the same operation to every element of an array, Map does seem more semantically direct. For instance:

test2D = {{a1, a2, a3}, {b1, b2}, {c1, c2, c3, c4}};
Table[g[test2D[[row, col]]] + 1, {row, 1, Length@test2D}, {col, 1, Length@test2D[[row]]}];
MatrixForm[%, TableAlignments -> Left]
Map[g[#] + 1 &, test2D, {2}];
MatrixForm[%, TableAlignments -> Left]

But when I need to carry out index-specific (i.e., position-specific) operations on higher-dimensional(>=2D) arrays, I find Map and its variants more challenging than Table.

For instance, suppose I want to raise each element in a 1D array to a power equal to its position. That I can do with either Table or MapIndexed:

test1D = {a1, a2, a3};
Table[test1D[[col]]^col, {col, 1, Length@test1D}]
Flatten[MapIndexed[#1^#2 &, test1D], 1]

But suppose I want to raise each element in a 2D array to a power equal to its row no. x column no. With Table that's conceptually straightforward:

Table[test2D[[row, col]]^(row*col), {row, 1, Length@test2D}, {col, 1, Length@test2D[[row]]}]

But how would one do that with MapIndexed? It would be nice if it were just something like:

MapIndexed[#1^(#2*#3) &, test2D]

where #2 were the column index and #3 were the row index, but it doesn't work like that.

Finally, suppose you have more detailed index-specific operations in a 2D array. That seems to be where Table really shines, but I'd be interested to hear of alternatives. E.g., suppose that, from each successive 4-element block of data in a row, you need to extract the 2nd and 4th elements, but only when all four elements are present. Thus, in a row of {a1, a2, a3, a4, a5, a6, a7, a8, a9, a10}, you need {{a2, a4}, {a6, a8}}. And you need to do this for each successive row. Further, the rows have variable lengths. With Table, this does the job:

test2Dx = {{a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, 
a14, a15, a16, a17, a18, a19, a20}, {b1, b2, b3, b4, b5, b6, b7, 
b8, b9, b10, b11}, {c1, c2, c3, c4, c5, c6, c7}, {d1, d2, d3, d4, 
d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17}};

Table[{test2Dx[[row, 2 + col*4]], test2Dx[[row, 4 + col*4]]}, {row, 1, Length@test2Dx}, {col, 0, (Floor[N[Length[test2Dx[[row]]]/4]]) - 1}];

MatrixForm[%, TableAlignments -> Left]

Is there a semantically straightforward way to do this using other functions (e.g., Map or its variants and a pure function)—or is this a use case for which Table makes more sense?

$\endgroup$
0

2 Answers 2

17
$\begingroup$

Many index-specific operations can be implemented via MapIndexed with a level specificaton. Your Power example can be written as:

MapIndexed[#1^(#2[[1]]*#2[[2]]) &, test2D, {2}]

If you want better readability of indices you can define an auxiliary function:

myPower[x_, {n1_, n2_}] := x^(n1 n2);
MapIndexed[myPower, test2D, {2}]

Some index-specific operations can be implemented without indices at all. The last example in your question can be coded in a functional form as:

Map[Downsample[#, 2, 2] &, Map[Partition[#, 4] &, test2Dx], {2}]

This expression can be also rewritten in a more verbose way:

splitInBlocksOf4 = Partition[#, 4] &;
takeEvenElements = Downsample[#, 2, 2] &;
Map[takeEvenElements, Map[splitInBlocksOf4, test2Dx], {2}]

In many cases, the functional approach is shorter, faster and less error-prone than index-based solutions.

$\endgroup$
7
  • $\begingroup$ Just a side note: if speed is concerned, the definition myPower[x_, {n1_, n2_}] … should be avoided because function definition based on pattern matching can't be auto-compiled. Related: mathematica.stackexchange.com/a/705/1871 $\endgroup$
    – xzczd
    Commented Aug 3, 2019 at 11:36
  • $\begingroup$ @Shadowray. This is a very nice answer since, in addition to providing working syntax that addresses each of my questions, you also offer verbose versions of each to help with reader understanding. Much appreciated! $\endgroup$
    – theorist
    Commented Aug 3, 2019 at 15:34
  • $\begingroup$ @Shadowray. Also, I see that, because my target indices were {2nd, 4th}, these could be easily captured with a simple Downsampling by 2's within each 4-element block. But suppose I had a different array (call it test2Dy) with blocks of, say, length 11, and wanted to pull out the 3rd and 7th members of each. In that case, would Downsample still work easily, or might Part be preferred, as follows?: Map[{#[[3]], #[[7]]} &, Map[Partition[#, 11] &, test2Dy], {2}] or, equivalently, {#[[3]], #[[7]]} & /@ Partition[#, 11] & /@ test2Dy. $\endgroup$
    – theorist
    Commented Aug 4, 2019 at 0:17
  • 1
    $\begingroup$ @theorist I would prefer an expression which closer reflects my intentions. If I want to specifically extract elements number 3 and 7 (e.g. because 3 describes apples and 7 describes oranges), then I will use explicit indices {#[[3]], #[[7]]} &. If I, for example, want to reduce the number of elements by factor of four and avoid the elements which are too close to the ends of a list, then I will use something like Take[#, {3, -3, 4}]&. This way it will be easier to read and maintain in the future. $\endgroup$
    – Ray Shadow
    Commented Aug 4, 2019 at 1:07
  • 1
    $\begingroup$ @theorist Downsample is a high-level function which internally uses Part. In addition it performs a lot of checks. (See GeneralUtilities`PrintDefinitions[Downsample]). While Part is a built-in kernel function. That's why using Part directly is much faster. $\endgroup$
    – Ray Shadow
    Commented Aug 4, 2019 at 23:04
10
$\begingroup$

We don't need to avoid Table in my view. In cases that Table is more straightforward, just use Table. If speed is concerned, Compile it. Here is an example:

Can I generate a "piecewise" list from a list in a fast and elegant way?

Nevertheless, your 2 examples (especially 2nd one) don't belong to the cases that Table is more straightforward, at least for someone familiar enough with list manipulation of Mathematica, I'm afraid. Do remember list manipulation is more than "Map and its variants".

The following is my solution:

# /@ #@test2D &[#^Range@Length@# &]   

Partition[#, 2] &@#[[2 ;; ;; 2]] & /@ test2Dx

BTW if test2D is not ragged i.e. ArrayQ returns True for test2D, I'll:

{dim1, dim2} = Range@Dimensions@test2D    
((test2D^dim1)\[Transpose]^dim2)\[Transpose]
(* Alternatively: *)
test2D^Outer[Times, dim1, dim2]
$\endgroup$
1
  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$
    – Kuba
    Commented Aug 4, 2019 at 22:25

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.