Function std.algorithm.iteration.cacheBidirectional
eagerly evaluates cache
front
of
on range
each
construction or call to popFront
,
to store the result in a cache
.
The result is then directly returned when front
is called,
rather than re-evaluated.
This can be a useful function to place in a chain, after functions
that have expensive evaluation, as a lazy alternative to std.array.array
.
In particular, it can be placed after a call to
, or before a call
to map
.
filter
may provide bidirectional iteration if needed, but since
this comes at an increased cost, it must be explicitly requested via the
call to cache
. Furthermore, a bidirectional cacheBidirectional
cache
will
evaluate the "center" element twice, when there is only one element left in
the range
.
does not provide random access primitives,
as cache
would be unable to cache
cache
the random accesses.
If Range
provides slicing primitives,
then
will provide the same slicing primitives,
but cache
hasSlicing!Cache
will not yield true (as the std.range.primitives.hasSlicing
trait also checks for random access).
Prototype
auto cacheBidirectional(Range)( Range range ) if (isBidirectionalRange!Range);
Example
import std.algorithm.comparison : equal; import std.stdio, std.range; import std.typecons : tuple; ulong counter = 0; double fun(int x) { ++counter; // http://en.wikipedia.org/wiki/Quartic_function return ( (x + 4.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) ) / 14.0 + 0.5; } // Without cache, with array (greedy) auto result1 = iota(-4, 5).map!(a =>tuple(a, fun(a)))() .filter!"a[1]<0"() .map!"a[0]"() .array(); // the values of x that have a negative y are: assert(equal(result1, [-3, -2, 2])); // Check how many times fun was evaluated. // As many times as the number of items in both source and result. assert(counter == iota(-4, 5).length + result1.length); counter = 0; // Without array, with cache (lazy) auto result2 = iota(-4, 5).map!(a =>tuple(a, fun(a)))() .cache() .filter!"a[1]<0"() .map!"a[0]"(); // the values of x that have a negative y are: assert(equal(result2, [-3, -2, 2])); // Check how many times fun was evaluated. // Only as many times as the number of items in source. assert(counter == iota(-4, 5).length);
Example
Tip
is eager when evaluating elements. If calling front on the
underlying cache
range
has a side effect, it will be observeable before calling
front on the actual cached range
.
Furtermore, care should be taken composing
with std.range.take.
By placing cache
take
before
, then cache
will be "aware"
of when the cache
range
ends, and correctly stop caching elements when needed.
If calling front has no side effect though, placing take
after
may yield a faster cache
range
.
Either way, the resulting ranges will be equivalent, but maybe not at the same cost or side effects.
import std.algorithm.comparison : equal; import std.range; int i = 0; auto r = iota(0, 4).tee!((a){i = a;}, No.pipeOnPop); auto r1 = r.take(3).cache(); auto r2 = r.cache().take(3); assert(equal(r1, [0, 1, 2])); assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared. assert(equal(r2, [0, 1, 2])); assert(i == 3); //cache has accessed 3. It is still stored internally by cache.