35

I have a list of functions:

fns = {f, g, h}

and a list of triples:

list = {{1,2,3},{11,22,33},{111,222,333},{1111,2222,3333}};

What's the best way to apply f to the first element of every triple, g to the second elements, and h to the last elements?

{
  {f[1], g[2], h[3]}, 
  {f[11], g[22], h[33]}, 
  {f[111], g[222], h[333]}, 
  {f[1111], g[2222], h[3333]}
}

(I know a few methods, but I'm looking for more.)

Brett Champion
  • 20,779
  • 2
  • 64
  • 121
  • Suddenly everybody is using Compose; I think I started a trend. – Mr.Wizard Sep 30 '12 at 06:48
  • 1
    @J.M. I know, which is why I'm amused that three answers below are using it. – Mr.Wizard Sep 30 '12 at 09:50
  • 1
    @Mr.Wizard You sure it were you? Have a look here, here, and here,for example :-). – Leonid Shifrin Sep 30 '12 at 13:26
  • 1
    @Leonid three replies come to mind: (1) You don't think I actually read all that stuff do you? (2) I'm senile and I have no idea why they trust me with the keys. (3) Great minds think alike. -- Take your pick. – Mr.Wizard Sep 30 '12 at 13:42
  • 1
    @Mr.Wizard I'dpick #3 :-). Besides, neither of us started the trend, it was started by the designer of mma, responsible for Compose (in this case, most likely Stephen Wolfram himself). – Leonid Shifrin Sep 30 '12 at 13:46
  • @J.M. I think it might be obsolete because it feels somewhat misnamed. A shame, because it is squatting on a decent shorter name for Composition -- a mistake I still make from time to time when writing code. The semantics of Compose are also a bit of a mixed bag: part Lisp funcall and part Composition. The former meaning (#@##2&) is what people are using for this question, and I wouldn't mind seeing another name given to that -- much like Identity is a name for #&. Call perhaps, or the lengthy FunctionCall? – WReach Sep 30 '12 at 14:01
  • 1
    @WReach, if my opinion counts for anything, I was bummed when they "replaced" Compose[] with Composition[]. Oh well... – J. M.'s missing motivation Sep 30 '12 at 14:06
  • Why did't anybody use Composite instead of Compose if indeed, Compose is being retired? – Gary Boswell Oct 03 '12 at 16:58
  • @Gary I presume you mean Composition -- it wouldn't really be applicable here as I see it. – Mr.Wizard Oct 04 '12 at 03:20
  • Brett, it's your perogative to Accept whatever answer you want, but why did you select what appears to be an inferior method? – Mr.Wizard Oct 04 '12 at 03:21
  • 1
    @Mr.Wizard My reasons are completely irrational and focus more on what I consider elegant or interesting approaches rather than absolute speed. So I liked the approaches that used Inner and ListCorrelate over faster ones that used Map. (I should also say that my final code will probably end up using Leonid's double Transpose. And for what it's worth, there was also much up-voting involved on my part prior to finally accepting an answer.) – Brett Champion Oct 04 '12 at 04:21

10 Answers10

32

How about:

Inner[#2@#1 &, list, fns, List, 2]

or

Inner[Compose, fns, Transpose@list, List] (* Note, that Compose is obsolete *)

or

MapIndexed[fns[[Last@#2]]@#1 &, list, {2}]

or

ListCorrelate[{fns}, list, {1, -1}, {}, Compose, Sequence]

or

MapThread[Compose, {Array[fns &, Length@list], list}, 2]

or

ReplacePart[list, {i_, j_} :> fns[[j]][list[[i, j]]]]

or

list // Query[All, Thread[Range@Length@fns -> fns]]

or (cheating a little)

list // Query[All, {1 -> f, 2 -> g, 3 -> h}]
WReach
  • 68,832
  • 4
  • 164
  • 269
26

What about

Map[MapThread[Compose, {fns, #}] &, list]

or

Transpose@MapThread[Map, {fns, Transpose[list]}]
Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • 1
    Like J.M. says Compose has been obsolete since more than 20(!) years. – stevenvh Oct 03 '12 at 17:13
  • 1
    @stevenvh And so :-) ? – Leonid Shifrin Oct 03 '12 at 17:15
  • 2
    @steven, it may not exactly be recommended, but it still works. From an old army saw: "if a dumb thing works, then it ain't dumb." – J. M.'s missing motivation Oct 03 '12 at 17:23
  • @Leonid - The documentation center doesn't give any description of it any more, so how can it help OP, when he doesn't know what it does? (You don't explain it either) – stevenvh Oct 04 '12 at 15:53
  • @stevenvh Fair enough. I will add the description. But I guess the OP would know what it is, in this particular case, given his affiliation :-) – Leonid Shifrin Oct 04 '12 at 15:58
  • So I wonder, what should one write instead of the obsolete Compose in e.g. Inner[Compose, fns, Transpose@list, List], if one does not want to use Slot-s? What the heck is the full name of the function application function, if there is any?? – István Zachar Jul 17 '16 at 08:50
  • @IstvánZachar I'd still use Compose. I do use it myself sometimes in such cases. It may be obsolete, but I very much doubt that the support for it would ever be discontinued. – Leonid Shifrin Jul 17 '16 at 11:24
23

The OP said: "I know a few methods, but I'm looking for more." so here are my offerings for the sake of interest. The second is intentionally a bit convoluted. The third may actually be of interest as the method could be used for in-place modification.

With[{op = MapIndexed[#[Slot @@ #2] &, fns]}, op & @@@ list]

Fold[RotateLeft@MapAt[#2, #, 1] &, list\[Transpose], Function[x, x /@ # &] /@ fns]\[Transpose]

Module[{x = list\[Transpose]}, Table[x[[i]] = fns[[i]] /@ x[[i]], {i, Length@x}]; x\[Transpose]]

Or for in-place modification:

With[{x = list}, Table[x[[All, i]] = fns[[i]] /@ x[[All, i]], {i, Length@First@x}]; x]

This post is primarily to provide the service of comparative timings. I will be using Mathematica 7.

Timings using an array of 1.5 million Integers and three inert symbolic heads:

fns = {f, g, h};
list = RandomInteger[1*^6, {500000, 3}];
times = timeAvg[#[]] & /@ methods;
BarChart[MapThread[Labeled, {times, methods}]]

Mathematica graphics

Using an array of Reals and three trig functions:

fns = {Sin, Cos, Csc};
list = RandomReal[1*^6, {500000, 3}];
times = timeAvg[#[]] & /@ methods;
BarChart[MapThread[Labeled, {times, methods}]]

Mathematica graphics

To explore performance with different shapes here is as above but with 500 random trig functions:

fns = RandomChoice[{Sin, Cos, Sec, Csc, Tan}, 500];
list = RandomReal[1*^6, {5000, 500}];
times = timeAvg[#[]] & /@ methods;
BarChart[MapThread[Labeled, {times, methods}]]

Mathematica graphics


Functions as I named and used them:

SetAttributes[timeAvg, HoldFirst]
timeAvg[func_] := 
  Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 15}]

leonid1[] := Map[MapThread[Compose, {fns, #}] &, list]
leonid2[] := Transpose@MapThread[Map, {fns, Transpose[list]}]
rm1[]     := Replace[list, x_List :> MapIndexed[fns[[First@#2]]@#1 &, x], {1}]
rm2[]     := MapIndexed[fns[[First@#2]]@#1 &, #] & /@ list
kguler1[] := Inner[#1@#2 &, fns, #, List] & /@ list
kguler2[] := Inner[Compose, fns, #, List] & /@ list
wreach1[] := Inner[#2@#1 &, list, fns, List, 2]
wreach2[] := MapIndexed[fns[[Last@#2]]@#1 &, list, {2}]
wreach3[] := ListCorrelate[{fns}, list, {1, -1}, {}, Compose, Sequence]
wreach4[] := MapThread[Compose, {Array[fns &, Length@list], list}, 2]
wizard1[] := With[{op = MapIndexed[#[Slot @@ #2] &, fns]}, op & @@@ list]
wizard2[] := Fold[RotateLeft@MapAt[#2, #, 1] &, list\[Transpose], Function[x, x /@ # &] /@ fns]\[Transpose]
wizard3[] := Module[{x = list\[Transpose]}, Table[x[[i]] = fns[[i]] /@ x[[i]], {i, Length@x}]; x\[Transpose]]

methods = {leonid1, leonid2, rm1, rm2, kguler1, kguler2, wreach1, 
   wreach2, wreach3, wreach4, wizard1, wizard2, wizard3};
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
15
Inner[#1@#2 &, fns, #, List] & /@ list
(*or *)
Inner[Compose, fns, #, List] & /@ list
% //TableForm

enter image description here

kglr
  • 394,356
  • 18
  • 477
  • 896
11

Another solution using MapIndexed and —

  1. Replace:

    Replace[list, x : {_, _, _} :> MapIndexed[fns[[First@#2]]@#1 &, x], {1}]
    
  2. Map:

    MapIndexed[fns[[First@#2]]@#1 &, #] & /@ list
    
rm -rf
  • 88,781
  • 21
  • 293
  • 472
7

Here is another option using Compose:

Compose@@@Thread@{fns, #}&/@list

or with Function:

Thread[fns~Function[{f, v}, f@v, Listable]~#] & /@ list
Murta
  • 26,275
  • 6
  • 76
  • 166
7

This is not a complete answer, but I am posting it as one so it does not get lost in the comments. Compose has returned in Version 12 as Construct, so all solutions can now be implemented with supported and documented functions.

Daniel W
  • 3,416
  • 18
  • 31
  • 3
    Maybe should note that Compose[f, x] is equivalent to Construct[f, x] but Compose[f, g, x] is not equivalent to Construct[f, g, x]. – Michael E2 Jan 08 '20 at 21:00
  • @MichaelE2, thank you for noting this. I suspect from watching the design reviews online that there may be a builtin function that will solve this whole problem in 12.1 as it seems to have caught Wolfram's attention. – Daniel W Jan 08 '20 at 21:25
7

Sorry for bothering an old question. It's not that fast but here is one way with the newer Threaded:

Function[, #1@#2, {Listable}][Threaded@fns, list]
Silvia
  • 27,556
  • 3
  • 84
  • 164
  • 2
    (+1) I like updates like this one!!! – bmf Jan 11 '23 at 12:00
  • 3
    @bmf Thanks! Though not the fastest, the performance of Threaded is not bad. I guess people on this site haven't got into habit of using it (yet). – Silvia Jan 11 '23 at 12:13
3

Yet another solution:

Transpose[Activate[Thread[Inactive[Map][fns, Transpose[list]]]]]
Gustavo Delfino
  • 8,348
  • 1
  • 28
  • 58
2

Just another way:

Transpose@Diagonal[(Thread /@ Through[fns[#]]) & /@ (Transpose@list)]

({{f[1], g[2], h[3]}, {f[11], g[22], h[33]}, {f[111], g[222], h[333]}, {f[1111], g[2222], h[3333]}})

Or using Outer:

Transpose@Diagonal[Transpose@*Thread@*Through /@ Outer[fns @@ #1 &@*List,Transpose@list]]
E. Chan-López
  • 23,117
  • 3
  • 21
  • 44