A systematic approach seems to be in using iterators. An iterator is a data structure which returns a given part of a list on demand. Here is a possible implementation for the case at hand:
ClearAll[makeIterator];
makeIterator[f_, chunkSize_, n_] :=
Module[{ctr = 0},
iterator[
Function[
If[ctr >= n,
None,
With[{result = Table[f[], {Min[chunkSize, n - ctr]}]},
ctr += Length[result];
result
]]]]];
We need to add one generic method for an iterator:
ClearAll[getNext];
getNext[iterator[f_]] := f[]
To test, you can define e.g.
iter = makeIterator[f, 10, 105]
and then call getNext[iter] a few times.
The next ingredient is an auxiliary function which I will call merge:
ClearAll[merge];
merge[tally1_, tally2_] :=
Transpose[{#[[All, 1, 1]], Total[#[[All, All, 2]], {2}]}] &@
GatherBy[tally1~Join~tally2, First]
this merges the counts of two different tallied lists. Finally:
lazyTally[i_iterator] :=
FixedPoint[
With[{next = getNext[i]},
If[next === None, #, merge[#, Tally@next]]
] &,
{}]
We can benchmark:
AbsoluteTiming[fTally[f,n];]
AbsoluteTiming[Tally@Table[f[],{n}];]
lazyTally[makeIterator[f,1000,n]]//AbsoluteTiming
(*
{0.3583984,Null}
{0.0058593,Null}
{0.0156250,{{4,24811},{1,24963},{3,25233},{2,24993}}}
*)
You get a 100-fold efficiency gain in memory for about 3-fold loss in runtime efficiency, for this size of the chunk of entire list (which you can play with)
Tallywith a user-defined function (outside of compilation, which restricts type). Nevertheless I'll try. – Mr.Wizard Jan 31 '13 at 13:50Do[c[#] += 1 & @ f[], {n}]– Mr.Wizard Jan 31 '13 at 14:23Trace[c[f[]] +=1 ]-- there's a double evaluation off[]. – Mr.Wizard Jan 31 '13 at 14:35Incrementwhich isHoldFirst. – Leonid Shifrin Jan 31 '13 at 14:36