cached

A _range algorithm that caches the elements of a _range by evaluating them only once.

Makes a new cache object that will be kept alive collectively with the slices that it produces, first of which is returned.

As a natural benefit of caching, the returned _range is a ForwardRange even if the source _range is an InputRange. Although the returned _range provides opIndex, it is a RandomAccessRange only if the source _range hasLength.

The version that takes buffers may never need to allocate memory if the number of elements never exceed the capacity of the underlying CircularBlocks. (See CircularBlocks.)

This range may mutate the underlying element blocks as needed even inside its front() inout function. For that reason, the behavior of some of its operations are undefined if the provided buffers live on read-only memory.

  1. auto cached(R range, size_t heapBlockCapacity)
    cached
    (
    R
    )
    (,
    size_t heapBlockCapacity = pageSize / ElementType!R.sizeof
    )
  2. auto cached(R range, ubyte[N][M] buffers)

Parameters

range R

the source _range to cache elements of

heapBlockCapacity size_t

the minimum capacity to use for CircularBlocks, which is used as storage for the cached elements; the default value attempts to use one page of memory

Return Value

Type: auto

A _range that participates in the collective ownership of the cached elements

Bugs

Although opIndex is provided, isRandomAccessRange produces false because this implementation is not a BidirectionalRange at this time.

Todo:

Add opApplyReverse support and BidirectionalRange functions

Examples

// In the following typical usage, even though `slide(2)` would visit most
// elements more than once, `cached` guarantees that the lambda function
// executed by `map` will be executed only once per range element.

auto r = iota(4)
         .map!(i => i * i)    // [0, 1, 4, 9]
         .cached
         .slide(2)
         .array;

// There are 3 sliding windows of width 2 over 4 elements
r.length.shouldBe(3);
r[0].shouldBe([0, 1]);
r[1].shouldBe([1, 4]);
r[2].shouldBe([4, 9]);
// Random access over an InputRange

import std.algorithm : splitter;
import std.range : popFrontN;

// Make an InputRange
auto source() {
    auto lines = "monday,tuesday,wednesday,thursday,friday,saturday,sunday";
    return lines.splitter(',');
}

// The source range does not provide random access:
static assert(!__traits(compiles, source[0]));

// The cached range does:
auto c = source.cached;
assert(c[2] == "wednesday");  // Note out-of-order indexing
assert(c[1] == "tuesday");    // This element is ready thanks to the previous line

c.popFrontN(3);

// Now index 0 is another element
assert(c[0] == "thursday");
// This version uses an existing buffer. It may never need to allocate
// additional heap memory.

ubyte[1024][2] buffers;
auto r = iota(4)
         .map!(i => i * i)
         .cached(buffers)    // Will use the provided buffers
         .slide(2)
         .array;

r.length.shouldBe(3);

Meta