Threading automatically with Listable functions requires the argument expressions to have the same length (or for one of them to be atomic). For nested lists the threading will continue down through the levels, provided the lengths are the same at each level. So, for example, these all work because the Dimensions of the two lists are the same at the first $n$ levels:

But there is an obvious interpretation of the above code, which is to map the addition over the outer level of the first argument, i.e. to add the second 5x3 array to each of the ten 5x3 arrays in the first argument.

A more easily visualised example is adding an offset to a list of coordinates:

Neither of these is particularly readable though. It would be nice to have a "smart" threading function that would identify that the second dimension of coords (length 2) matches the first dimensions of offset (also length 2), allowing the code to be written very readably:

The function works by examining the dimensions of both input lists to determine which dimensions are "shared" (ie. have the same length) by both inputs. One of the input lists (the one with the greater ArrayDepth) is transposed such that the shared dimensions are outermost. This allows the function to thread over those dimensions, after which the Transpose is reversed.

Straightforward examples

By "straightforward", I mean cases where there is no ambiguity about how to match up the dimensions of the two input lists. For Listable functions like Plus and Times the smartThread function works as you would expect:

Dimension matching ambiguites

There is not always a single unique way to match up the dimensions of the input lists. Consider for example smartThread[a + b] where Dimensions[a] = {2, 3, 2} and Dimensions[b] = {3, 2}. The default behaviour of smartThread is to match the dimensions innermost first, so the result would be equivalent to {a[[1]] + b, a[[2]] + b}:

More generally, setting the second argument to n causes the code to select the n'th valid permutation of the dimensions.

Note:
I would personally recommend thinking twice before using smartThread in cases where there is ambiguity over how to match dimensions in the input lists. The motivation for writing it was to allow simple constructs like smartThread[coordinateList + offset], increasing readability for code where the intention is intuitively obvious. In situations where that isn't the case, it potentially makes the code less clear than using something like Map or Transpose explicitly.

Thanks, @Rojo, good catch. I must have only tested it on Orderless functions...
–
Simon WoodsApr 16 '13 at 18:00

I've patched it up using a third argument to indicate if a and b have been swapped, but I'm trying to think of a neater way to handle it.
–
Simon WoodsApr 16 '13 at 18:10

I'm still trying to get a clear idea of the general behaviour expected. For example, is the output of smartThread@f[{{x[1], x[2]}}, {{y[1]}, {y[2]}}] and smartThread@f[{{x[1]}, {x[2]}}, {{y[1], y[2]}}] what you expect? I mean, the dimensions of the output can depend on the order of the lists? Or is that a bonus case that doesn't need to be handled?
–
RojoApr 16 '13 at 18:18

Mathematica is a registered trademark of Wolfram Research, Inc. While the mark is used herein with the limited permission of Wolfram Research, Stack Exchange and this site disclaim all affiliation therewith.