There are two obvious ways to structure a linked list in Mathematica, "left":
{1, {2, {3, {4, {5, {6, {7, {}}}}}}}}
And "right":
{{{{{{{{}, 7}, 6}, 5}, 4}, 3}, 2}, 1}
These can be made with:
toLeftLL = Fold[{#2, #} &, {}, Reverse@#] & ;
toRightLL = Fold[List, {}, Reverse@#] & ;
If I use these, and do a simple ReplaceRepeated
to walk through the linked list, I get drastically different Timing
results:
r = Range[15000];
left = toLeftLL@r;
right = toRightLL@r;
Timing[i = 0; left //. {head_, tail_} :> (i++; tail); i]
Timing[i = 0; right //. {tail_, head_} :> (i++; tail); i]
(* Out[6]= {0.016, 15000} *)
(* Out[7]= {5.437, 15000} *)
Why?
ReplaceRepeated
uses SameQ
to determine when to stop applying the rule.
When SameQ
compares two lists, it checks lengths, and if the same, then applies SameQ
to elements from the first to the last. In the case of left
the first element is an integer, so it is easy to detect distinct lists, while for right
list the first element is the deeply nested expression, so it needs to traverse it. This is the reason for the slowness.
In[25]:= AbsoluteTiming[
Do[Extract[right, ConstantArray[1, k]] ===
Extract[right, ConstantArray[1, k + 1]], {k, 0, 15000 - 1}]]
Out[25]= {11.7091708, Null}
Now compare this with:
In[31]:= Timing[i = 0; right //. {tail_, head_} :> (i++; tail); i]
Out[31]= {5.351, 15000}
ReplaceRepeated
does not provide such an option, so we should use FixedPoint
and ReplaceAll
:
In[61]:= Timing[i = 0;
FixedPoint[(# /. {tail_, _} :> (i++; tail)) &, right,
SameTest ->
Function[
If[ListQ[#1] && ListQ[#2] &&
Length[#1] ==
Length[#2], (#1 === {} && #2 === {}) || (Last[#1] ===
Last[#2]), #1 === #2]]]; i]
Out[61]= {0.343, 15000}
In[162]:= Timing[i = 0;
NestWhile[Function[# /. {tail_, head_} :> (i++; tail)], right,
Function[# =!= {}]]; i]
Out[162]= {0.124, 15000}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With