Recursive Function
Let's start with simple recursive function provided by @corey979:
ClearAll[fRecursive]
fRecursive[1] = 2;
fRecursive[n_] := fRecursive[n] =
Count[Table[fRecursive[k], {k, 1, n-2}], fRecursive[n - 1]]
It works as expected:
Array[fRecursive, 15]
(* {2, 0, 0, 1, 0, 2, 1, 1, 2, 2, 3, 0, 3, 1, 3} *)
but it's a bit slow:
Table[fRecursive[i], {i, 10^4}] // MaxMemoryUsed // AbsoluteTiming
(* {23.9591, 2043888} *)
Non-recursive Function
General
To write faster version, let's think what knowledge, about previous elements, do we need, in order to calculate next element.
In fRecursive
, in each n
-th step, we create list of all elements up to n-2
(since memoization is used we don't recalculate them) and count number of occurrences of n-1
element in that list. If n-1
element is in the list it means that in some previous step we already had to calculate number of its occurrences in a sub-list of currently used list, so if we would carry information about previous counts, between iterations, we could possibly speedup calculations.
As a "counter" we need a mapping from non-negative integers, representing elements of sequence, we want to generate, to non-negative integers representing number of occurrences of those elements in sequence generated so far. Since elements of sequence are counts of some elements in already generated sequence, they can't be greater than total number of sequence elements, that we want to generate (except first element which can be arbitrary large), thus our mapping can be implemented as an array of integers with length equal to maximal index of element we want to generate.
Initially there are no elements in sequence, so we start with "counter" array of zeros. In each step we take "current" element of sequence, since it appears in sequence, we increment value of "counter" associated with current element, and take result as new "current" element for next iteration. Our expected sequence is list of consecutive "current" values.
Above algorithm is implemented in following function, which gives elements with indices from min
to max
from sequence starting with n0
value:
ClearAll[fGeneral]
fGeneral = Compile[{{n0, _Integer}, {min, _Integer}, {max, _Integer}},
Module[{current = -1, counter = Table[0, {max + 2}]},
counter[[1]] = n0;
Do[current = counter[[current + 2]]++, {min - 1}];
Table[current = counter[[current + 2]]++, {max - min + 1}]
],
RuntimeOptions -> "Speed", CompilationTarget -> "C"
];
We get expected results:
fGeneral[2, 1, 15]
(* {2, 0, 0, 1, 0, 2, 1, 1, 2, 2, 3, 0, 3, 1, 3} *)
fGeneral[2, 10, 15]
(* {2, 3, 0, 3, 1, 3} *)
same as with recursive function:
fGeneral[2, 1, 10^3] === Array[fRecursive, 10^3]
(* True *)
but much faster and using much less memory:
fGeneral[2, 1, 10^4] // MaxMemoryUsed // AbsoluteTiming
(* {0.00008, 320560} *)
Note that since our counter
can handle at most max
values, above function will give an error if n0
value is greater than max
.
We'll deal with this special case later.
Looking for a Pattern
Now to find more optimizations let's look at table of values of our sequence as in answer of @m_goldberg and answer of @ubpdqn.
Table[
Module[{max = 100, list, n0SecondPos, n0PlusOnePos},
list = fGeneral[n0, 1, max];
n0SecondPos = Position[list, n0, {1}, 2][[2, 1]];
n0PlusOnePos =
Replace[Position[list, n0 + 1, {1}, 1], {{{i_}} :> i, {} -> max + 1}];
Join[
Take[list, 1],
Style[#, Red] & /@ Take[list, {2, n0SecondPos}],
Take[list, {n0SecondPos + 1, n0PlusOnePos - 1}],
Style[#, Blue] & /@ Take[list, {n0PlusOnePos, -1}]
]
],
{n0, 0, 10}
] // TableForm
(click on image to enlarge it)
Small Elements
We can see that up to second occurrence of first sequence element, there are always consecutive integers, starting from zero, interlaced with zeros (red elements).
So elements up to 2 n0 + 2
can be generated using following function, taking advantage of fast, built-in vectorized operations:
ClearAll[fSmall]
fSmall = Function[{n0, min, max},
Module[{result = Quotient[Range[min - 2, max - 2], 2]},
result[[(2 - Mod[min, 2]) ;; ;; 2]] = 0;
If[min === 1, result[[1]] = n0];
result
]
];
We get expected results:
fSmall[2, 1, 2 2 + 2]
(* {2, 0, 0, 1, 0, 2} *)
same as with fGeneral
function:
fSmall[2, 1, 2 2 + 2] === fGeneral[2, 1, 2 2 + 2]
(* True *)
fSmall[10^7, 1, 2 10^7 + 2] === fGeneral[10^7, 1, 2 10^7 + 2]
(* True *)
but faster and using less memory:
fSmall[10^7, 1, 2 10^7 + 2] // MaxMemoryUsed // AbsoluteTiming
(* {0.269374, 320001984} *)
fGeneral[10^7, 1, 2 10^7 + 2] // MaxMemoryUsed // AbsoluteTiming
(* {0.424444, 640000640} *)
We' ll use fSmall
for generating "relatively small" sequences i.e. those with max <= 2 n0 + 2
.
Note that this will automatically take care of special cases for which fGeneral
doesn't work, i.e. those with max < n0
Large Elements
Second relevant observation is generalization of discovery made by @Watson and @m_goldberg. Starting from first occurrence of n0+1
value, we can see a simple pattern (blue elements).
Dependence of position of first n0+1
value on n0
can be found by Mathematica:
Table[Position[fGeneral[n0, 1, 10^3], n0 + 1, {1}, 1][[1, 1]], {n0, 30}] //
FindSequenceFunction
(* 3 + 2 #1 + #1^2 & *)
For elements starting from n0^2 + 2 n0 + 3
we can see pattern described by m_goldberg and Watson.
partitionedTable = Module[{min = #^2 + 2 # + 3, k = 2 # + 2},
Partition[fGeneral[#, min, min + 10 k], k] // TableForm
] &;
partitionedTable@2
$\begin{array}{cccccc}
3 & 0 & 3 & 1 & 3 & 2 \\
4 & 0 & 4 & 1 & 4 & 2 \\
5 & 0 & 5 & 1 & 5 & 2 \\
6 & 0 & 6 & 1 & 6 & 2 \\
7 & 0 & 7 & 1 & 7 & 2 \\
8 & 0 & 8 & 1 & 8 & 2 \\
9 & 0 & 9 & 1 & 9 & 2 \\
10 & 0 & 10 & 1 & 10 & 2 \\
11 & 0 & 11 & 1 & 11 & 2 \\
12 & 0 & 12 & 1 & 12 & 2 \\
\end{array}$
partitionedTable@5
$\begin{array}{cccccccccccc}
6 & 0 & 6 & 1 & 6 & 2 & 6 & 3 & 6 & 4 & 6 & 5 \\
7 & 0 & 7 & 1 & 7 & 2 & 7 & 3 & 7 & 4 & 7 & 5 \\
8 & 0 & 8 & 1 & 8 & 2 & 8 & 3 & 8 & 4 & 8 & 5 \\
9 & 0 & 9 & 1 & 9 & 2 & 9 & 3 & 9 & 4 & 9 & 5 \\
10 & 0 & 10 & 1 & 10 & 2 & 10 & 3 & 10 & 4 & 10 & 5 \\
11 & 0 & 11 & 1 & 11 & 2 & 11 & 3 & 11 & 4 & 11 & 5 \\
12 & 0 & 12 & 1 & 12 & 2 & 12 & 3 & 12 & 4 & 12 & 5 \\
13 & 0 & 13 & 1 & 13 & 2 & 13 & 3 & 13 & 4 & 13 & 5 \\
14 & 0 & 14 & 1 & 14 & 2 & 14 & 3 & 14 & 4 & 14 & 5 \\
15 & 0 & 15 & 1 & 15 & 2 & 15 & 3 & 15 & 4 & 15 & 5 \\
\end{array}$
partitionedTable@9
$\begin{array}{cccccccccccccccccccc}
10 & 0 & 10 & 1 & 10 & 2 & 10 & 3 & 10 & 4 & 10 & 5 & 10 & 6 & 10 & 7 & 10 & 8 & 10 & 9 \\
11 & 0 & 11 & 1 & 11 & 2 & 11 & 3 & 11 & 4 & 11 & 5 & 11 & 6 & 11 & 7 & 11 & 8 & 11 & 9 \\
12 & 0 & 12 & 1 & 12 & 2 & 12 & 3 & 12 & 4 & 12 & 5 & 12 & 6 & 12 & 7 & 12 & 8 & 12 & 9 \\
13 & 0 & 13 & 1 & 13 & 2 & 13 & 3 & 13 & 4 & 13 & 5 & 13 & 6 & 13 & 7 & 13 & 8 & 13 & 9 \\
14 & 0 & 14 & 1 & 14 & 2 & 14 & 3 & 14 & 4 & 14 & 5 & 14 & 6 & 14 & 7 & 14 & 8 & 14 & 9 \\
15 & 0 & 15 & 1 & 15 & 2 & 15 & 3 & 15 & 4 & 15 & 5 & 15 & 6 & 15 & 7 & 15 & 8 & 15 & 9 \\
16 & 0 & 16 & 1 & 16 & 2 & 16 & 3 & 16 & 4 & 16 & 5 & 16 & 6 & 16 & 7 & 16 & 8 & 16 & 9 \\
17 & 0 & 17 & 1 & 17 & 2 & 17 & 3 & 17 & 4 & 17 & 5 & 17 & 6 & 17 & 7 & 17 & 8 & 17 & 9 \\
18 & 0 & 18 & 1 & 18 & 2 & 18 & 3 & 18 & 4 & 18 & 5 & 18 & 6 & 18 & 7 & 18 & 8 & 18 & 9 \\
19 & 0 & 19 & 1 & 19 & 2 & 19 & 3 & 19 & 4 & 19 & 5 & 19 & 6 & 19 & 7 & 19 & 8 & 19 & 9 \\
\end{array}$
Lists with elements following above pattern can be generated using following function:
ClearAll[fLarge]
fLarge = Compile[{{n0, _Integer}, {min, _Integer}, {max, _Integer}},
Module[{shift = n0^2 + 2 n0 + 3},
Table[
If[EvenQ[i],
Quotient[i, 2 n0 + 2] + n0 + 1
(* else *),
Quotient[Mod[i, 2 n0 + 2] - 1, 2]
],
{i, min - shift, max - shift}
]
],
RuntimeOptions -> "Speed", CompilationTarget -> "C"
];
It gives expected results:
fLarge[2, 2^2 + 2 2 + 3, 15]
(* {3, 0, 3, 1, 3} *)
same as with fGeneral
function:
fLarge[2, 2^2 + 2 2 + 3, 10^7] === fGeneral[2, 2^2 + 2 2 + 3, 10^7]
(* True *)
fLarge[10^2, 10^4 + 2 10^2 + 3, 10^7] === fGeneral[10^2, 10^4 + 2 10^2 + 3, 10^7]
(* True *)
generation of large lists is slower than with fGeneral
:
fLarge[2, 2^2 + 2 2 + 3, 10^7] // MaxMemoryUsed // AbsoluteTiming
(* {0.556881, 240000192} *)
fGeneral[2, 2^2 + 2 2 + 3, 10^7] // MaxMemoryUsed // AbsoluteTiming
(* {0.11998, 320000320} *)
but algorithm used in fLarge
, in contrast to fGeneral
, doesn't need to generate all previous elements so it's much faster when we need to generate sequence of elements with relatively high min
index.
For example generation of only one element is much faster and uses almost no memory:
fLarge[2, 10^7, 10^7] // MaxMemoryUsed // AbsoluteTiming
(* {0.000016, 352} *)
fGeneral[2, 10^7, 10^7] // MaxMemoryUsed // AbsoluteTiming
(* {0.059807, 80000480} *)
Empirically fLarge
becomes faster, more or less, when min > 9/10 max
(at least at my computer):
fLarge[2, 9 10^6, 10^7] // MaxMemoryUsed // AbsoluteTiming
(* {0.056439, 24000472} *)
fGeneral[2, 9 10^6, 10^7] // MaxMemoryUsed // AbsoluteTiming
(* {0.05865, 104000600} *)
Final Function
Putting all three functions together we get:
ClearAll[fInternal]
fInternal = Function[{n0, min, max},
Which[
max <= 2 n0 + 2,
fSmall[n0, min, max],
9 max < 10 min && min >= n0^2 + 2 n0 + 3,
fLarge[n0, min, max],
True,
fGeneral[n0, min, max]
]
];
Our final function with basic tests on argument patterns:
ClearAll[f]
f[n0_Integer?NonNegative, {min_Integer?Positive, max_Integer?Positive}] /; min <= max := fInternal[n0, min, max]
f[n0_Integer?NonNegative, max_Integer?Positive] := fInternal[n0, 1, max]
f[n0_Integer?NonNegative, {n_Integer?Positive}] := First@fInternal[n0, n, n]
First argument of f
function is first element of sequence, second argument is standard sequence specification, so {n}
will give n
-th element of sequence, {min, max}
will give list containing elements from min
to max
, max
will give elements from 1 to max
.
f[2, 15]
(* {2, 0, 0, 1, 0, 2, 1, 1, 2, 2, 3, 0, 3, 1, 3} *)
f[2, {9, 15}]
(* {2, 2, 3, 0, 3, 1, 3} *)
f[2, {15}]
(* 3 *)
Table
, iteratork
starts from 1, i.e. the first element of the table isf[1-1]=f[0]
which is undefined. Second, I think the upper limit wasn't supposed to bek-1
if you're iterating overk
. $\endgroup$Table[x[i],{i,1,0}]
to be{}
. Please clarify. $\endgroup$f[n_] := Count[Table[f[k - 1], {k, 2, n - 1}], f[n - 1]]
(i.e.,n-2
changed ton-1
); nowTable[f[i], {i, 1, 14}]
gives{2, 0, 0, 1, 0, 2, 1, 1, 2, 2, 3, 0, 3, 1}
. $\endgroup$