In Mathematica, I create singly linked lists like so:
toLinkedList[x_List] := Fold[pair[#2, #1] &, pair[], Reverse[x]];
fromLinkedList[ll_pair] := List @@ Flatten[ll];
emptyQ[pair[]] := True;
emptyQ[_pair] := False;
Using the symbol pair
for the cons cells has the advantage of Flatten
working safely even if the lists contain Mathematica-style List
s, and allows you to define custom notation using MakeExpression
/MakeBoxes
, which makes everything much more pleasant. In order to avoid having to muck around with $IterationLimit
, I wrote functions to work with these lists using either While
loops or NestWhile
instead of using recursion. Naturally, I wanted to see which approach would be faster, so I wrote two candidates so I could watch 'em fight:
nestLength[ll_pair] :=
With[{step = {#[[1, -1]], #[[-1]] + 1} &},
Last@NestWhile[step, {ll, 0}, ! emptyQ@First@# &]];
whileLength[ll_pair] :=
Module[{result = 0, current = ll},
While[! emptyQ@current,
current = current[[2]];
++result];
result];
The results were very strange. I tested the functions on linked lists of length 10000, and whileLength
was usually about 50% faster, at about 0.035 seconds to nestLength
's 0.055 seconds. However, occasionally whileLength
would take about ~4 seconds. I thought there might be some caching behavior, so I started generating fresh, random lists to check, and whileLength
wouldn't necessarily be slow on the first run with a new list; it might take dozens of times to see the slowdown, but then it wouldn't recur (at least not for the 200 runs I was trying with each list).
What might be going on?
For reference, the function I used for testing is this:
getTimes[f_, n_] :=
With[{ll = toLinkedList@RandomInteger[100, 10000]},
Table[Timing[f@ll], {n}][[All, 1]]]
EDIT: I neglected to mention the version earlier; I got these results with Mathematica 8.
EDIT the second: When I read Daniel Lichtblau's answer, I realized that my times for "typical" runs omitted a leading 0. It's been fixed.
EDIT the third: I think Leonid Shifrin is correct to associate the issue with Module
; I can get the same sort of behavior from the NestWhile
-based version by replacing the With
with a Module
:
nestModuleLength[ll_pair] :=
Module[{step = {#[[1, -1]], #[[-1]] + 1} &},
Last@NestWhile[step, {ll, 0}, ! emptyQ@First@# &]];
In[15]:= Select[getTimes[nestModuleLength, 100], # > 3 &]
Out[15]= {3.797}
List
generated byRandomInteger
, which is immediately converted into a tree-like expression. – PillsyModule
- @belisarius was the first to suggest that it was the culprit. – Leonid Shifrin