Hi J.—now that you mention it, I forgot to cover that in the review session like I had said I would, didn't I? My apologies. Let's do it now:
Thread is pretty extensible, but—for me—it's a little bit haphazard/ad hoc compared to its compatriots Map and MapThread , which are much more structured.
Thread[f[{a,b,c}] takes f and threads it across the arguments which are fed to it in list form, so it gives {f[a],f[b],f[c]} , just like map works on lists with Map[f,{a,b,c}] yielding {f[a],f[b],f[c]} .
Thread doesn't just work on singular lists, though: Thread[f[{a,b,c},x]] will take that single x and duplicate it across the elements of the list in order to maintain a consistent final dimensionality, yielding {f[a,x],f[b,x],f[c,x]} . Note that if you think about the arguments of f being combined into a list of lists, {{a,x},{b,x},{c,x}} , Thread is stripping that internal list structure and giving the equivalent result to f@@@{{a,x},{b,x},{c,x}} .
- Provided two (or more) lists,
Thread will provide the respective elements of each list as a sequence (that is, a sequence of elements with no list structure, like the arguments to a function) to the threaded function, as is done above with the singleton x . So: Thread[f[{a,b,c},{1,2,3}]]=={f[a,1],f[b,2],f[c,3]} .
- You can also mix and match these: try providing the reverse of what you see in the second subpoint here, or e.g.
Thread[f[a,{x,y,z},b]] or even Thread[f[a,{x,y,z},b,{1,2,3}]] .
Thread also can work over heads other than List , as long as you provide the head you'd like to thread over: Thread[f[a+b]] vs. Thread[f[a+b],Plus] should exemplify this. (Map automatically works over different heads with no specification—it's just that the most commonly used one is List .)
Thread can also function on only the first n arguments with Thread[f[arg1,arg2,arg3],h,n] . (This is not available for Map .)
- Finally,
Thread doesn't Hold its arguments and will evaluate in situations where you might not expect, which can cause some unpleasant surprises.
- My estimation:
Thread can let you do some very neat things very efficiently, but it's a little bit kitchen sink for my personal general use. Over time, you'll get a sense of where it might be the best function to use, and you'll then deploy it in those contexts.
Map is the "bread and butter" for taking a given function and operating it at a specific level.
Map[f,expr] takes a function f and "inserts" it at level 1 into expr . That's usually a list, but it doesn't need to be: Map[f,{a,b,c}]=={f[a],f[b],f[c]} and Map[f,a+b+c]==f[a]+f[b]+f[c] serve as examples.
- More generally,
Map doesn't care about the Head of expr , so we could say: Map[f,g[a,b,c]]==g[f[a],f[b],f[c]] . Hopefully you see how this general case reduces to the two specific ones prior.
- With no level specified,
Map works at level 1 only. We can specify a level with Map[f,expr,n] .
- If
n is given just as an integer, Map will operate the function "down to" level n from level 1. This is easiest to see with a nested list; try this code to visualize it: Manipulate[TreeForm@Map[f, {{{3, 2}, {6, 7}}, {{9, 8}, {4, 1}}}, n], {n, 1, 3, 1}] .
- If
n is given in braces (as {n} ), then Map will operate the function only at that level. Analogously: Manipulate[TreeForm@Map[f, {{{3, 2}, {6, 7}}, {{9, 8}, {4, 1}}}, {n}], {n, 1, 3, 1}] .
n can be negative, though in the case of non-rectangular expressions this can produce irregular results, as we discussed previously.
Map has many related functions which can be incredibly valuable when you need just slightly different functionality. Cf. MapAt , MapIndexed , KeyMap , KeyValueMap , and things like the newer SubsetMap .
Map should generally be your go-to function for "repeated function application". It's comparatively easy to understand and specify precisely what you want.
MapThread , as the name suggests, is a bit between these two functions—I might say that it acts like a simplified Thread that has many of the good qualities that you get by working with Map .
MapThread makes you explicitly specify the function and its arguments separately, leaving less to "interpretation".
MapThread[f,{{a,b,c},{x,y,z}}] gives you {f[a,x],f[b,y],f[c,z]} , and that kind of explains the primary idea.
- Like
Thread , you can provide as many lists as you like as arguments to be inserted into f : MapThread[f,{list1,list2,...}] works like Thread[f[list1,list2,...]] but in a more "transparent" way. (Plus, it holds evaluation until f is fed the sequence list1[[1]],list2[[1]],... , which is nice.)
- Unlike
Thread , you cannot specify a head other than List over which to thread f .
- Unlike
Map , MapThread only works on lists (and associations, which I'll explain shortly).
- Like
Map , you can specify a level like: MapThread[f,{list1,list2,...},n] . MapThread just takes the integer without a list format, as "down to" in the context of MapThread doesn't usually contextually make much sense.
MapThread is also coded to work with associations, so MapThread[f, {<|a -> 1, b -> 3|>, <|a -> 2, b -> 4|>}]==<|a -> f[1, 2], b -> f[3, 4]|> .
MapThread is the function of choice for when you have "parallel" lists of data and you want to combine them in some way where all their first elements are taken as a group, all their second elements are taken as a group, and so on. It's less confusing—but correspondingly less capable—version of Thread with some of the syntactic capabilities of Map in terms of how and where you can use it.
This is a lot of information, but hopefully it provides you with the overview you were looking for :).
|