44
$\begingroup$

Say I have a group of functions:

f1[a_] := a * -1;
f2[a_] := a * 100;
f3[a_] := a / 10.0;

and some data in a list:

data := Range[1, 20];

I would like to apply this group of functions to the data: the first function applied to the first item of data, the second to the second, and so on. Because there are more data elements than there are functions, the first function is also applied to the fourth data element, and so on.

A simple work-round is this:

Flatten[{f1[#[[1]]], f2[#[[2]]], f3[#[[3]]]}  & /@ Partition[data, 3]]

giving

{-1, 200, 0.3, -4, 500, 0.6, -7, 800, 0.9, -10, 1100, 1.2, -13, 1400, 
  1.5, -16, 1700, 1.8}

but this isn't an ideal solution: the slots have been 'hard-wired', and it wouldn't be possible to modify the list of functions easily.

Is there a Map-related function that could do this elegantly? I've not been able to discover it yet.

(This is a toy example, of course!)

$\endgroup$
3
  • 3
    $\begingroup$ your current solution only goes up to 18, is that intentional? $\endgroup$
    – acl
    Commented Apr 3, 2012 at 10:22
  • 6
    $\begingroup$ Congrats on the 1024th question of this site! $\endgroup$
    – F'x
    Commented Apr 3, 2012 at 12:02
  • 8
    $\begingroup$ Oh good, a nice round number: xkcd.com/1000 $\endgroup$
    – tkott
    Commented Apr 3, 2012 at 14:04

8 Answers 8

27
$\begingroup$

Here's how you can do it in a simple way:

functionMap[funcs_List, data_] := Module[{fn = RotateRight[funcs]}, 
    First[(fn = RotateLeft[fn])][#] & /@ data]

Use it as:

functionMap[{f1, f2, f3}, Range[20]]
(* {f1[1], f2[2], f3[3], f1[4], f2[5], f3[6], f1[7], f2[8], f3[9], f1[10],
    f2[11], f3[12], f1[13], f2[14], f3[15], f1[16], f2[17], f3[18], f1[19], f2[20]} *)
$\endgroup$
3
  • 2
    $\begingroup$ Oooo, clever. +1 (you know I like that approach.) $\endgroup$
    – Mr.Wizard
    Commented Apr 9, 2012 at 15:22
  • 2
    $\begingroup$ You could write it as a closure: makeRotator[funcs_] := Module[{fn = funcs}, First[(fn = RotateLeft[fn])][#] &] then makeRotator[{f1,f2,f3}] /@ Range[10]. $\endgroup$
    – Szabolcs
    Commented May 25, 2012 at 6:50
  • 1
    $\begingroup$ Shorter/simpler: {fn = funcs} and Last[(fn = RotateLeft[fn])] $\endgroup$
    – Mr.Wizard
    Commented Jan 4, 2013 at 18:01
32
$\begingroup$

Use PadRight with the cyclical padding setup:

funcs = {f1, f2, f3};
data = Range[1, 20];
MapThread[#1[#2] &, {PadRight[funcs, Length@data, funcs], data}]

or

MapThread[Compose, {PadRight[funcs, Length@data, funcs], data}]

{f1[1], f2[2], f3[3], f1[4], f2[5], f3[6], f1[7], f2[8], f3[9], f1[10], f2[11], f3[12], f1[13], f2[14], f3[15], f1[16], f2[17], f3[18], f1[19], f2[20]}

$\endgroup$
3
  • 1
    $\begingroup$ You'll get a populist badge pretty soon :) Congrats in advance! $\endgroup$
    – rm -rf
    Commented May 24, 2012 at 21:56
  • $\begingroup$ I wrote a solution myself before looking at the answers. It was exactly this one, nearly character by character. :) $\endgroup$
    – Szabolcs
    Commented May 25, 2012 at 6:44
  • $\begingroup$ @Szabolcs: Great minds when meet... :) Thanks R.M, you should know that usually I use your method. $\endgroup$ Commented May 25, 2012 at 8:02
21
$\begingroup$
MapIndexed[{f1, f2, f3}[[Mod[First@#2, 3, 1]]][#1] &, data]

does what you want (thanks to Sjoerd for pointing out a silly inefficiency).

$\endgroup$
2
  • 1
    $\begingroup$ This is more or less what I was thinking about too, but I wouldn't give the arguments to the functions first. In this way you're doing 3 times more calculations than necessary. Just put a [#1] after the Part and leave the function list alone. $\endgroup$ Commented Apr 3, 2012 at 10:44
  • $\begingroup$ @Sjoerd yes good point, also, this way it generalizes better. thanks $\endgroup$
    – acl
    Commented Apr 3, 2012 at 10:58
20
$\begingroup$

Another approach

#1@#2 & @@@ 
  Partition[Riffle[Range[20], {f1, f2, f3}, {1, -1, 2}], 2] 

Comparing with Acl's solution:

#1@#2 & @@@ 
  Partition[Riffle[Range[20], {f1, f2, f3}, {1, -1, 2}], 2] == 
 MapIndexed[{f1@#1, f2@#1, f3@#1}[[Mod[First@#2, 3, 1]]] &, data

==> True

$\endgroup$
1
  • 1
    $\begingroup$ nice one +1. Can save few key strokes with #2@#1 & @@@ Partition[Riffle[Range[20], {f1, f2, f3}], 2]:) $\endgroup$
    – kglr
    Commented Apr 3, 2012 at 14:02
18
$\begingroup$

Another approach is to use Outer, as follows

Flatten@Outer[#1[#2]&, {f1, f2, f3}, Range[1,5]]
(*
{f1[1], f1[2], f1[3], f1[4], f1[5], 
 f2[1], f2[2], f2[3], f2[4], f2[5],
 f3[1], f3[2], f3[3], f3[4], f3[5]}
*)

Unfortunately, Outer causes problems if the data points are vectors, for instance

Flatten@Outer[#1[#2] &, {f1, f2, f3}, {#, #} & /@ Range[1, 3]]

produces

{f1[1], f1[1], f1[2], f1[2], f1[3], f1[3], 
 f2[1], f2[1], f2[2], f2[2], f2[3], f2[3], 
 f3[1], f3[1], f3[2], f3[2], f3[3], f3[3]}

To work around this, you need to use the 3$^\text{rd}$ and subsequent parameters of Outer which are level specifications:

Outer[#1[#2] &, {f1, f2, f3}, {#, #} & /@ Range[1, 3], Infinity, 1] //
  Flatten
(*
{f1[{1, 1}], f1[{2, 2}], f1[{3, 3}], 
 f2[{1, 1}], f2[{2, 2}], f2[{3, 3}], 
 f3[{1, 1}], f3[{2, 2}], f3[{3, 3}]}
*)

Or, if you prefer

Outer[#1 @@ #2 &, {f1, f2, f3}, {#, #} & /@ Range[1, 3], Infinity, 1] //
  Flatten
(*
{f1[1, 1], f1[2, 2], f1[3, 3], 
 f2[1, 1], f2[2, 2], f2[3, 3], 
 f3[1, 1], f3[2, 2], f3[3, 3]}
*)
$\endgroup$
17
$\begingroup$

Another approach using Fold in combination with Sow/Reap:

Reap[Fold[(Sow[#[[1]][#2]]; RotateLeft[#]) &, {f1, f2, f3}, data];][[2, 1]]
$\endgroup$
8
$\begingroup$

Would the following qualify as elegant or not?

mapFunctions[funcs_, list_] := Module[{r = list, u},
  applyFunc[f_, l_, i_, t_] := 
   MapAt[f, l, Table[{u}, {u, i, Length[l], t}]];
  Do[r = applyFunc[funcs[[u]], r, u, Length[funcs]],
   {u, Length[funcs]}];
  Return[r];
  ]

which gives:

In[9]:= MapFunctions[{f1, f2, f3}, data]

Out[9]= {f1[1], f2[2], f3[3], f1[4], f2[5], f3[6], f1[7], f2[8], 
 f3[9], f1[10], f2[11], f3[12], f1[13], f2[14], f3[15], f1[16], 
 f2[17], f3[18], f1[19], f2[20]}
$\endgroup$
2
  • 3
    $\begingroup$ Well, it has a Do. What would you say? ;-) $\endgroup$ Commented Apr 3, 2012 at 10:46
  • 1
    $\begingroup$ @SjoerdC.deVries but it has no Goto, I swear! $\endgroup$
    – F'x
    Commented Apr 3, 2012 at 11:14
1
$\begingroup$

Using Query

f1[a_] := a * -1;
f2[a_] := a * 100;
f3[a_] := a / 10.0;

data = Partition[Range @ 18, 3]

Query[All, {1 -> f1, 2 -> f2, 3 -> f3}] @ data

{{-1, 200, 0.3}, {-4, 500, 0.6}, {-7, 800, 0.9}, {-10, 1100, 1.2}, {-13, 1400, 1.5}, {-16, 1700, 1.8}}

In a simple case like this we don't need the f - definitions:

Query[All, Thread[{1, 2, 3} -> {# * -1 &, # * 100 &, # / 10. &}]] @ data

This gives, of course, the same result.

$\endgroup$

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.