Recursive pure functions (#0
) seem to be one of the darker corners of the language. Here are a couple of non-trivial examples of their use , where this is really useful (not that they can not be done without it). The following is a pretty concise and reasonably fast function to find connected components in a graph, given a list of edges specified as pairs of vertices:
ClearAll[setNew, componentsBFLS];
setNew[x_, x_] := Null;
setNew[lhs_, rhs_]:=lhs:=Function[Null, (#1 := #0[##]); #2, HoldFirst][lhs, rhs];
componentsBFLS[lst_List] := Module[{f}, setNew @@@ Map[f, lst, {2}];
GatherBy[Tally[Flatten@lst][[All, 1]], f]];
What happens here is that we first map a dummy symbol on each of the vertex numbers, and then set up a way that, given a pair of vertices {f[5],f[10]}
, say, then f[5]
would evaluate to f[10]
. The recursive pure function is used as a path compressor (to set up memoization in such a way that instead of long chains like f[1]=f[3],f[3]=f[4],f[4]=f[2], ...
, memoized values get corrected whenever a new "root" of the component is discovered. This gives a significant speed-up. Because we use assignment, we need it to be HoldAll, which makes this construct even more obscure and more attractive ). This function is a result of on and off-line Mathgroup discussion involving Fred Simons, Szabolcs Horvat, DrMajorBob and yours truly. Example:
In[13]:= largeTest=RandomInteger[{1,80000},{40000,2}];
In[14]:= componentsBFLS[largeTest]//Short//Timing
Out[14]= {0.828,{{33686,62711,64315,11760,35384,45604,10212,52552,63986,
<<8>>,40962,7294,63002,38018,46533,26503,43515,73143,5932},<<10522>>}}
It is certainly much slower than a built-in, but for the size of code, quite fast still IMO.
Another example: here is a recursive realization of Select
, based on linked lists and recursive pure functions:
selLLNaive[x_List, test_] :=
Flatten[If[TrueQ[test[#1]],
{#1, If[#2 === {}, {}, #0 @@ #2]},
If[#2 === {}, {}, #0 @@ #2]] & @@ Fold[{#2, #1} &, {}, Reverse[x]]];
For example,
In[5]:= Block[
{$RecursionLimit= Infinity},
selLLNaive[Range[3000],EvenQ]]//Short//Timing
Out[5]= {0.047,{2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,
<<1470>>,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,
2992,2994,2996,2998,3000}}
It is however not properly tail recursive, and will blow the stack (crash the kernel) for larger lists. Here is the tail-recursive version:
selLLTailRec[x_List, test_] :=
Flatten[
If[Last[#1] === {},
If[TrueQ[test[First[#1]]],
{#2, First[#1]}, #2],
(* else *)
#0[Last[#1],
If[TrueQ[test[First[#1]]], {#2, First[#1]}, #2]
]] &[Fold[{#2, #1} &, {}, Reverse[x]], {}]];
For example,
In[6]:= Block[{$IterationLimit= Infinity},
selLLTailRec[Range[500000],EvenQ]]//Short//Timing
Out[6]= {2.39,{2,4,6,8,10,12,14,16,18,20,22,
<<249978>>,499980,499982,499984,499986,499988,499990,499992,
499994,499996,499998,500000}}