Adding a lazy version of std.algorithm.iteration.cache #10687 Open Feature (#10709)

* added Lazycache feature

* Added Lazycache funtion

* changes as per review

* Update std/algorithm/iteration.d

Co-authored-by: Richard (Rikki) Andrew Cattermole <pbdadmin@gmail.com>

* removing unrelated change

* added lazychache in cheatsheet

* removed error

* add_lazycache_function.dd

* changes in changelog

* lines changes in changelog

* removing whitspace

---------

Co-authored-by: Richard (Rikki) Andrew Cattermole <pbdadmin@gmail.com>
This commit is contained in:
Aditya Chincholkar 2025-03-29 03:53:30 +05:30 committed by GitHub
parent b7395ea7f5
commit 1de571c710
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 214 additions and 0 deletions

View file

@ -0,0 +1,15 @@
Add lazyCache to std.algorithm.iteration
The new `lazyCache` function provides a lazily evaluated range caching mechanism.
Unlike `cache`, which eagerly evaluates range elements during construction,
`lazyCache` defers evaluation until elements are explicitly requested.
---
auto result = iota(-4, 5).map!(a => tuple(a, expensiveComputation(a)))().lazyCache();
// No computations performed at this point
auto firstElement = result.front;
// First element is now evaluated
---
See the $(REF lazyCache, std,algorithm,iteration) documentation for more details.

View file

@ -8,6 +8,8 @@ $(BOOKTABLE Cheat Sheet,
$(TR $(TH Function Name) $(TH Description))
$(T2 cache,
Eagerly evaluates and caches another range's `front`.)
$(T2 lazyCache,
Lazily evaluates and caches another range's `front`, unlike `cache`.)
$(T2 cacheBidirectional,
As above, but also provides `back` and `popBack`.)
$(T2 chunkBy,
@ -420,6 +422,203 @@ private struct _Cache(R, bool bidir)
}
}
/**
* Similar to `cache`, but lazily evaluates the elements. Unlike `cache`,
* this function does not eagerly evaluate the front of the range until
* it's explicitly requested.
*
* This can be useful when evaluation of range elements has side effects that
* should be delayed until actually needed.
*
* See_Also: cache
*
* Params:
* range = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
*
* Returns:
* An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with the lazily cached values of range
*/
auto lazyCache(Range)(Range range)
if (isInputRange!Range)
{
return LazyCache!(Range, isBidirectionalRange!Range)(range);
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range, std.stdio;
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;
}
// With lazyCache, front won't be evaluated until requested
counter = 0;
auto result = iota(-4, 5).map!(a => tuple(a, fun(a)))()
.lazyCache();
// At this point, no elements have been evaluated yet
assert(counter == 0);
// Now access the first element
auto firstElement = result.front;
// Only now the first element is evaluated
assert(counter == 1);
// Process the result lazily
auto filtered = result.filter!(a => a[1] < 0)()
.map!(a => a[0])();
// Values are calculated as we iterate
assert(equal(filtered, [-3, -2, 2]));
// Only elements we actually accessed were evaluated
assert(counter == iota(-4, 5).length);
}
private struct LazyCache(R, bool bidir)
{
import core.exception : RangeError;
private
{
import std.algorithm.internal : algoFormat;
import std.meta : AliasSeq;
alias E = ElementType!R;
alias UE = Unqual!E;
R source;
static if (bidir) alias CacheTypes = AliasSeq!(UE, UE);
else alias CacheTypes = AliasSeq!UE;
CacheTypes caches;
// Flags to track if front/back have been cached
bool frontCached = false;
static if (bidir) bool backCached = false;
static assert(isAssignable!(UE, E) && is(UE : E),
algoFormat(
"Cannot instantiate range with %s because %s elements are not assignable to %s.",
R.stringof,
E.stringof,
UE.stringof
)
);
}
this(R range)
{
source = range;
// Don't eagerly evaluate anything in the constructor
}
static if (isInfinite!R)
enum empty = false;
else
bool empty() @property
{
return source.empty;
}
mixin ImplementLength!source;
E front() @property
{
version (assert) if (empty) throw new RangeError();
// Only evaluate front if it hasn't been cached yet
if (!frontCached && !source.empty)
{
caches[0] = source.front;
frontCached = true;
}
return caches[0];
}
static if (bidir) E back() @property
{
version (assert) if (empty) throw new RangeError();
// Only evaluate back if it hasn't been cached yet
if (!backCached && !source.empty)
{
caches[1] = source.back;
backCached = true;
}
return caches[1];
}
void popFront()
{
version (assert) if (empty) throw new RangeError();
source.popFront();
// Reset the cache state for front
frontCached = false;
}
static if (bidir) void popBack()
{
version (assert) if (empty) throw new RangeError();
source.popBack();
// Reset the cache state for back
backCached = false;
}
static if (isForwardRange!R)
{
private this(R source, ref CacheTypes caches, bool frontCached, bool backCached = false)
{
this.source = source;
this.caches = caches;
this.frontCached = frontCached;
static if (bidir) this.backCached = backCached;
}
typeof(this) save() @property
{
static if (bidir)
return typeof(this)(source.save, caches, frontCached, backCached);
else
return typeof(this)(source.save, caches, frontCached);
}
}
static if (hasSlicing!R)
{
enum hasEndSlicing = is(typeof(source[size_t.max .. $]));
static if (hasEndSlicing)
{
private static struct DollarToken{}
enum opDollar = DollarToken.init;
auto opSlice(size_t low, DollarToken)
{
return typeof(this)(source[low .. $]);
}
}
static if (!isInfinite!R)
{
typeof(this) opSlice(size_t low, size_t high)
{
return typeof(this)(source[low .. high]);
}
}
else static if (hasEndSlicing)
{
auto opSlice(size_t low, size_t high)
in
{
assert(low <= high, "Bounds error when slicing lazyCache.");
}
do
{
import std.range : takeExactly;
return this[low .. $].takeExactly(high - low);
}
}
}
}
/**
Implements the homonym function (also known as `transform`) present
in many languages of functional flavor. The call `map!(fun)(range)`