Function Repository Resource:

FoldRotate

Source Notebook

Nest a list of functions around a seed, folding in a list of elements

Contributed by: Daniel Robinson

ResourceFunction["FoldRotate"][{f1, f2, f3, }, x, {e1, e2, e3, }]

gives f1[x, e1], then nests the remaining f2, f3, , folding in the e2, e3, .

ResourceFunction["FoldRotate"][{f1, f2, f3, }, {e1, e2, e3, }]

is equivalent to ResourceFunction["FoldRotate"][{f1, f2, f3, }, e1, {e2, e3, }].

Details

FoldRotate is a generalisation of Fold, going from a single function in the first argument to a list of functions.
ResourceFunction["FoldRotate"][{f1, f2, , fn}, x, {e1, e2, , en}] yields fn[…f2[f1[x, e1], e2], … en].
FoldRotate[{f}, x, {e1, e2, e3, }] is equivalent to Fold[f, x, {e1, e2, e3, }].
FoldRotate[{f, f, f, …}, x, {e1, e2, e3, }] is equivalent to Fold[{f}, x, {e1, e2, e3, }], albeit slower.
In the case ResourceFunction["FoldRotate"][{f1, , fn}, x, {e1, , em}], if n<m, the functions fi will "rotate" from fn back around to f1. If n>m, only the first m functions will be used in the output.
Like Fold, ResourceFunction["FoldRotate"] can be exited with Throw ».
Unlike Fold, ResourceFunction["FoldRotate"] does not have any operator forms.

Examples

Basic Examples (7) 

Apply three functions (f, g and h) and three elements (a, b and c) to a seed (x):

In[1]:=
ResourceFunction["FoldRotate"][{f, g, h}, x, {a, b, c}]
Out[1]=

Use the 2-argument form to get the same result:

In[2]:=
ResourceFunction["FoldRotate"][{f, g, h}, {x, a, b, c}]
Out[2]=

If the number of functions is less than the number of elements, the functions will "rotate" round to fill out:

In[3]:=
ResourceFunction[
 "FoldRotate"][{f, g}, x, {a, b, c, d}] (* f, g used cyclically *)
Out[3]=

If the number of functions is greater than the number of elements, some may not be used:

In[4]:=
ResourceFunction["FoldRotate"][{f, g, h}, x, {b, c}] (* h not used *)
Out[4]=

Using an empty list of functions will return the list of elements along with the seed:

In[5]:=
ResourceFunction["FoldRotate"][{}, x, {a, b, c}]
Out[5]=

Using an empty list of elements and no seed will return an empty list:

In[6]:=
ResourceFunction["FoldRotate"][{f, g, h}, {}]
Out[6]=

Adding a seed effectively prevents the second list from being registered as empty:

In[7]:=
ResourceFunction["FoldRotate"][{f, g, h}, x, {}]
Out[7]=

Scope (5) 

FoldRotate can take an arbitrary number of functions or elements:

In[8]:=
ResourceFunction["FoldRotate"][Array[Subscript[f, #] &, 5], x, Array[Subscript[e, #] &, 8]]
Out[8]=

FoldRotate can take pure functions. The first slot (#1) refers to the current value (starting with the seed); the second slot (#2) refers to the element being folded in:

In[9]:=
ResourceFunction["FoldRotate"][{#1*#2 &, Power[#1, -#2] &}, 1, Range[7]]
Out[9]=

Use Sow and Reap to get intermediary results (similar to how FoldList gets intermediary results for Fold):

In[10]:=
Reap[ResourceFunction[
   "FoldRotate"][{(Sow[#]; #1*#2) &, (Sow[#]; Power[#1, -#2]) &}, 1, Range[7]]][[2, 1]]
Out[10]=

An iterator and piecewise function can be used in place of multiple functions:

In[11]:=
(* Apply p and q separately: *)
p = #1 + #2 &;
q = #1 - #2 &;

ResourceFunction["FoldRotate"][{(p[#1, #2]) &, (q[#1, #2]) &}, 1, Range[15]]
Out[12]=
In[13]:=
(* Apply p and q as one piecewise function, r: *)
i = 1;
r = If[EvenQ[i], p[#1, #2], q[#1, #2]] &;

ResourceFunction["FoldRotate"][{(++i; r[#1, #2]) &}, 1, Range[15]]
Out[14]=

FoldRotate can be exited early with Throw. Use FoldRotate (along with Sow and Reap) to generate the nth partial sum in the series 1 + 2 × 3 + 4 × 5 + …:

In[15]:=
Reap[ResourceFunction["FoldRotate"][{Sow@*Times, Sow@*Plus}, 1, Range[1, 15]]][[2, 1]]
Out[15]=

Exit early when the partial sum is a multiple of 7:

In[16]:=
Catch[
 ResourceFunction["FoldRotate"][
  {
   If[Mod[#, 7] == 0, Throw[#], #1*#2] &,
   If[Mod[#, 7] == 0, Throw[#], #1 + #2] &
   },
  1,
  Range[10]
  ]
 ]
Out[16]=

Applications (1) 

Calculate alternating totals of a list:

In[17]:=
ResourceFunction["FoldRotate"][{Subtract, Plus}, {a, b, c, d, e}]
Out[17]=
In[18]:=
ResourceFunction["FoldRotate"][{Plus, Subtract}, {a, b, c, d, e}]
Out[18]=

Properties and Relations (3) 

FoldRotate can be used to mimic the behaviour of Fold:

In[19]:=
ResourceFunction["FoldRotate"][{f}, x, {a, b, c}]
Out[19]=
In[20]:=
ResourceFunction["FoldRotate"][{f, f, f}, x, {a, b, c}]
Out[20]=
In[21]:=
Fold[f, x, {a, b, c}]
Out[21]=

In fact, if only one function is provided, FoldRotate (in its 3-argument form) reduces to Fold:

In[22]:=
ResourceFunction["FoldRotate"][{f}, x, {a}] // Trace
Out[22]=

In order to return an unevaluated expression, Defer (or HoldForm) must be used at every level:

In[23]:=
expr = ResourceFunction[
  "FoldRotate"][{Defer[#1*#2] &, Defer[Power[#1, -#2]] &}, 1, Range[5]]
Out[23]=

Evaluate the expression with a replacement rule:

In[24]:=
expr /. Defer -> Identity
Out[24]=

FoldRotate is generally slower than Fold, especially when using large number of functions:

In[25]:=
elements = RandomInteger[{0, 10}, 10^3];
functions = Array[f, Length[elements]];

Fold[f, x, elements]; // RepeatedTiming
Out[26]=
In[27]:=
ResourceFunction["FoldRotate"][functions, x, elements]; // RepeatedTiming
Out[27]=

Possible Issues (3) 

FoldRotate takes either two or three arguments:

In[28]:=
ResourceFunction["FoldRotate"][Subscript[arg, 1]]
Out[28]=
In[29]:=
ResourceFunction[
 "FoldRotate"][Subscript[arg, 1], Subscript[arg, 2], Subscript[arg, 3], Subscript[arg, 4]]
Out[29]=

The first and last arguments must be lists:

In[30]:=
ResourceFunction["FoldRotate"][f, x, {a, b, c}]
Out[30]=
In[31]:=
ResourceFunction["FoldRotate"][{f, g, h}, x, a]
Out[31]=

Given no arguments at all, FoldRotate will not evaluate:

In[32]:=
ResourceFunction["FoldRotate"][]
Out[32]=

Neat Examples (1) 

Create a nesting of frames cycling through three colours:

In[33]:=
i = 1;
colourFrame = Framed[Row[{#1, #2}], FrameStyle -> Switch[Mod[i, 3, 1], 1, Red, 2, Green, 3, Blue]] &;

ResourceFunction["FoldRotate"][
 {(++i; colourFrame[#1, #2]) &},
 "",
 Range[10]
 ]
Out[34]=

Publisher

Daniel Robinson

Version History

  • 1.0.0 – 16 June 2023

Source Metadata

Related Resources

Author Notes

One could add a check to see whether or not all the functions in the first argument were the same, i.e. FoldRotate[{f, f, f, …}, x, {e1, e2, …}]. If that were the case, then (like FoldRotate[{f}, x, {e1, e2, e3, }]) it could be reduced to Fold[f, x, {e1, e2, e3, }]. I decided not to do this because I thought the overhead wouldn't be worth it.

License Information