12

From the documentation, Thread's behavior on functions where the first parameter is a List and the second is an atomic expression, is this:

Thread[f[{a, b, c}, x]] 

(* {f[a, x], f[b, x], f[c, x]} *)

If the second argument of 'f' is not atomic, it is possible to thread over its first argument {a,b,c} while treating the 2nd argument as if it were atomic? For example, consider:

M = Table[i + j, {i, 1, 3}, {j, 1, 2}]

(* {{2, 3}, {3, 4}, {4, 5}} *)

Then,

Thread[f[{a, b, c}, M]]

(* {f[a, {2, 3}], f[b, {3, 4}], f[c, {4, 5}]} *)

This is understandable because M is not atomic so For example:

g[p_, M_ /; Dimensions[M] == {3, 2}] := {p, M}

Threading over g Doesn't work as intended:

Thread[g[{a, b, c}, M]]

(* {{a, {2, 3}}, {b, {3, 4}}, {c, {4, 5}}} *)

Worse still, the result above is due to a coincidence: Length@M==3. If this is not the case, Thread returns an error:

Thread::tdlen: Objects of unequal length in f[{a,b,c},{{2,3,4,5},{3,4,5,6}}] cannot be combined. >>

I've also considered wrapping M in some variation of Hold, but none yield atomic expressions. Is there a way to force Thread to treat the 2nd argument of the function as atomic?

The ultimate goal is to SetAttributes of the function to Listable.

alancalvitti
  • 15,143
  • 3
  • 27
  • 92
  • But ... what is your intended result? ... f[#, M] & /@ {a, b, c} ? – Dr. belisarius Aug 25 '12 at 21:32
  • @Verde, it should handle both f[#, M] & /@ {a, b, c} and f[#,M]& @ a. – alancalvitti Aug 25 '12 at 21:40
  • 1
    I'm really not quite sure what you are trying to achieve but doesn't ReleaseHold@Thread[f[{a, b, c}, Hold@M]] do what you want? – sebhofer Aug 25 '12 at 21:45
  • @sebhofer, your approach does solve it if Thread is called directly, but as described in the last part of the question and in the response to Verde's comment, the idea is to make the function Listable. Try SetAttributes[f, Listable] and then f[{a, b, c}, M] and f[{a, b, c}, Hold@M]. Is there workaround to this? – alancalvitti Aug 25 '12 at 21:53
  • 1
    If I understand the question correctly, I gave explicit solutions here and here, where I also explain the solutions in detail. – Leonid Shifrin Aug 25 '12 at 21:55
  • So you want the described functionality but without using any additional functions like Thread...? – sebhofer Aug 25 '12 at 22:07
  • @Mr.Wizard can I edit the title to include Listable? – alancalvitti Aug 25 '12 at 22:28
  • 1
    @Mr.Wizard The question does not strike me as being asked narrowly about Thread. The goal seems to thread a function over a list in a certain way, and Thread was just used as a seemingly most straightforward way to obtain the desired result. At least, this is how I interpreted it from the start. – Leonid Shifrin Aug 25 '12 at 22:41
  • Alan I respect Leonid's opinion. I have amended my answer to address that aspect of the question. – Mr.Wizard Aug 25 '12 at 22:56
  • Alan, I see that you have not Accepted an answer to this question. I believe you were going to Accept Leonid's as soon as you checked it. Did you forget or was it unsatisfactory? – Mr.Wizard Feb 12 '13 at 18:31
  • @Mr.Wizard, I haven't yet but thanks for the reminder. I'll try to test by this weekend. – alancalvitti Feb 14 '13 at 17:04

4 Answers4

10

I will reproduce two solutions from my book, one using Listable SubValues described here:

listThread[f_, x_, y_] :=
  Module[{auxf},
   SetAttributes[auxf, Listable];
   auxf[t_][z_] := f[t, z];
   Through[auxf[x][y]]];

and another one using pure functions with Listable attribute, described here:

halfListable[f_, x_, y_] := Function[t, f[t, y], Listable][x]

Here is an example:

listThread[f, {1, 2}, {3, 4, 5}]
halfListable[f, {1, 2}, {3, 4, 5}]

(*
   {f[1, {3, 4, 5}], f[2, {3, 4, 5}]}
   {f[1, {3, 4, 5}], f[2, {3, 4, 5}]}
*)

More explanations can be found in the linked sections of the book.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Thank you Leonid - so far this looks like the best hope, I will likely accept shortly (still looking through your web site) though I was hoping to avoid custom functions. Could WRI have implemented a Level option functionality for Listable to achieve this? – alancalvitti Aug 25 '12 at 22:29
  • 2
    @alancalvitti The probem is that Listable is an attribute, not a function. As such, it is wired quite deeply into the evaluation sequence. And evaluator does not have any tuning parameters, it is not even clear how you can define such parameters locally (so that they will apply to a specific head in a given place of code). – Leonid Shifrin Aug 25 '12 at 22:35
  • @alancalvitti Thanks for the accept. It's been a while :-). – Leonid Shifrin Feb 19 '13 at 19:56
7

Does this work as desired?

Thread[Unevaluated[f[{a, b, c}, M]]]
{f[a, {{2, 3}, {3, 4}, {4, 5}}],
 f[b, {{2, 3}, {3, 4}, {4, 5}}], 
 f[c, {{2, 3}, {3, 4}, {4, 5}}]}

Since apparently I was just being obstinate regarding the focus of this question I shall give in and address the extension of this behavior to a pseudo-Listable function. I still find the question underspecified in that interpretation.

ClearAll[f, a, b, c, m]

SetAttributes[f, HoldAll]
x : f[___, _List, ___] := Thread@Unevaluated@x
f[other___] := {other}

Now:

m = Table[i + j, {i, 1, 3}, {j, 1, 2}];

f[{a, b, c}, m]

f[m, {a, b, c}]
{{a, {{2, 3}, {3, 4}, {4, 5}}}, {b, {{2, 3}, {3, 4}, {4, 5}}}, {c, {{2, 3}, {3, 4}, {4, 5}}}}

{{{{2, 3}, {3, 4}, {4, 5}}, a}, {{{2, 3}, {3, 4}, {4, 5}}, b}, {{{2, 3}, {3, 4}, {4, 5}}, c}}

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Nicer than my suggestion (actually the correct way to do it) but I don't think it will satisfy the OP – sebhofer Aug 25 '12 at 22:06
  • @sebhofer Thanks; why not? – Mr.Wizard Aug 25 '12 at 22:07
  • If I understand correctly the OP wants a function with Listable which does exactly this without using Thread or such... That's what I gather from the comment above. Not totally sure though. – sebhofer Aug 25 '12 at 22:09
  • @sebhofer Then a new question is required as this one is clearly about Thread. (Final line notwithstanding.) – Mr.Wizard Aug 25 '12 at 22:12
  • @Mr.Wizard, on my MMA 8.0.4, Thread[Unevaluated[f[{a, b, c}, M]]] evaluates to an expression that begins with: {{{f[a,2],f[a,3]},... Can anyone duplicate this? – alancalvitti Aug 25 '12 at 22:27
  • @alancalvitti since this question so predominantly addressed Thread I think a new one specifically addressing Listable behavior is appropriate. I am surprised by the v8 result; have you tried restarting Mathematica to be certain of the behavior? – Mr.Wizard Aug 25 '12 at 22:29
  • 1
    @alancalvitti It does so if f is Listable, otherwise it returns the desired result. – sebhofer Aug 25 '12 at 22:31
  • @sebhofer, and Mr.Wizard - agreed, I just restarted and replicated your answer (without Listable attribute) – alancalvitti Aug 25 '12 at 22:33
  • @Mr.Wizard, I could not get the version using Unevaluated to work on lists with this function: f2[p_, data_] := Select[data, #[[1]] == p &] (after setting attributes as you did) and the same matrix m as above. f2[2,m] and f2[3,m] work, but f2[{2, 3}, m] gives {}. ? – alancalvitti Aug 26 '12 at 18:42
  • Same problem as above occurs when using ReleaseHold@Thread@Hold in lieu of Thread@Unevaluated – alancalvitti Aug 26 '12 at 19:01
  • @alancalvitti with this definition: ClearAll[f2, m]; SetAttributes[f2, HoldAll]; x : f2[___, _List, ___] := Thread@Unevaluated@x; f2[p_, data_] := Select[data, #[[1]] == p &]; m = Table[i + j, {i, 1, 3}, {j, 1, 2}]; this input: f2[{2, 3}, m] gives output: {{{2, 3}}, {{3, 4}}} -- I'll be in Chat for a while if you want to talk about this. – Mr.Wizard Aug 26 '12 at 20:51
  • @Mr.Wizard, indeed it works. The next challenge is to generalize when the data matrix m has a "frame" column headers consisting of lists (eg, {2012,08,27}) rather than Strings. I posted a similar comment to kguler's solution above. I'll be happy to chat sometime. – alancalvitti Aug 27 '12 at 20:50
5

How about:

ClearAll[f];
Thread[f[{a, b, c}, M], List, 1]
(* {f[a, {{2, 3}, {3, 4}, {4, 5}}], f[b, {{2, 3}, {3, 4}, {4, 5}}], 
   f[c, {{2, 3}, {3, 4}, {4, 5}}]}*)


Thread[g[{a, b, c}, M], List, 1]  // Grid

enter image description here

More related examples in Scope >> Sequence Specifications in docs >> Thread

kglr
  • 394,356
  • 18
  • 477
  • 896
  • +1 because this is useful information and the question is not well defined, but I interpret that the solution should also handle f[M, {a, b, c}] and this, as written, does not. – Mr.Wizard Aug 25 '12 at 22:25
  • I am mistaken, it does! Thread[f[M, {a, b, c}], List, {2}] (For some reason I didn't think that worked.) – Mr.Wizard Aug 25 '12 at 22:27
  • Very nice. Is there any way to make Listable work with these options? – alancalvitti Aug 25 '12 at 22:35
  • @alancalvitti, in effect you want to change the behavior of the attribute Listable to thread over selected arguments? I would defer to Leonid/Rojo/... on whether this is possible/advisable:) – kglr Aug 25 '12 at 23:06
  • alan, btw just noticed that @Leonid's answer and comment already adresses this question. – kglr Aug 25 '12 at 23:18
  • @kguler, I can't get the desired behavior with the following function: SelectInFrame[h_, data_] := Select[data, #[[1]] == h &]. Using the same M, Thread[SelectInFrame[2, M], List, 1] --> {{2}, {3}} (it should be {2,3}) and Thread[SelectInFrame[{2, 3}, M], List, 1] returns error: "Thread::tpos: Cannot thread over positions 1 through 1 in {}". ? – alancalvitti Aug 26 '12 at 18:59
  • Thread evaluates the whole expression before threading. You need to use Unevaluated to prevent this behavior. So, Thread[Unevaluated@SelectInFrame[2, M], List, 1] gives {{2,3}} and Thread[Unevaluated@SelectInFrame[{2, 3}, M], List, 1] gives {{{2, 3}}, {{3, 4}}}. – kglr Aug 27 '12 at 01:18
  • @kguler, can you extend this answer if the first column of M contains Lists, eg {2012,8,25} rather than Strings? I find that it works if the last argument to Thread is {2} to match a single such list, but doesn't match lists of lists, eg {{2012,8,25},{2012,8,27}} (assuming M contains those). – alancalvitti Aug 27 '12 at 20:33
  • @alan, can you give the examples of M and Thread usage mentioned in your comment where Thread[...,{2}] works/does not work? Is M something like m2 = {{{2007, 1, 30}, 3, "x"}, {{2008, 2, 23}, 4, "y"}, {{2009, 10, 14}, 5, "z"}}? – kglr Aug 27 '12 at 21:50
  • @kguler, yes, only the 1st row or 1st column (part of the "frame") elements are changed from String to List as you show - the remaining elements can stay numbers or anything else. I describe this issue more in my own answer to the question here: http://mathematica.stackexchange.com/questions/9702/emulating-r-data-frame-getters-with-upvalues – alancalvitti Aug 28 '12 at 01:14
  • @alan, I am not sure if this answers the question in your comment: with m2 as above, Thread[Unevaluated@ SelectInFrame[{{2008, 2, 23}, DateList[{2009, 10, 14}][[;; 3]]}, m2], List, 1] gives {{{{2008, 2, 23}, 4, "y"}}, {{{2009, 10, 14}, 5, "z"}}}. – kglr Aug 28 '12 at 02:40
  • @kguler, that works on lists of dates but not singletons (Thread[Unevaluated@SelectInFrame[{2007, 1, 30}, m2], List, 1] --> {{}, {}, {}} ) . The purpose of this Thread construction is to selectively map on lists but also on singleton arguments – alancalvitti Aug 28 '12 at 17:56
  • @kguler, ... I'm trying to emulate this function using Thread: MapIfList[f_, x_] := If[Length[x] > 0, Map[f, x], f[x]] – alancalvitti Aug 28 '12 at 17:57
2

The following works when f is Listable and has a definition.

M = Table[i + j, {i, 1, 3}, {j, 1, 2}];
SetAttributes[f,Listable]  
f[x_,y_]:={x,y}   
f[{a,b,c},Unevaluated@M]  
faysou
  • 10,999
  • 3
  • 50
  • 125