Report a bug
If you spot a problem with this page, click here to create a Github issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

mir.series

Index-series

The module contains Series data structure with special iteration and indexing methods. It is aimed to construct index or time-series using Mir and Phobos algorithms.

Public imports mir.ndslice.slice.

Authors:
Ilya Yaroshenko
Examples:
See_also: unionSeries, troykaSeries, troykaGalop.
import mir.ndslice;
import mir.series;

import mir.array.allocation: array;
import mir.algorithm.setops: multiwayUnion;

import std.datetime: Date;
static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; 
import std.exception: collectExceptionMsg;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [
    Date(2017, 01, 01),
    Date(2017, 03, 01),
    Date(2017, 04, 01)];

auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [
    Date(2017, 01, 01),
    Date(2017, 02, 01),
    Date(2017, 05, 01)];

auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);    

//////////////////////////////////////
// asSlice method
//////////////////////////////////////
assert(series0
    .asSlice
    // ref qualifier is optional
    .map!((ref key, ref value) => key.month == value)
    .all);

//////////////////////////////////////
// get* methods
//////////////////////////////////////

auto refDate = Date(2017, 03, 01);
auto missingDate = Date(2016, 03, 01);

// default value
double defaultValue = 100;
assert(series0.get(refDate, defaultValue) == 3);
assert(series0.get(missingDate, defaultValue) == defaultValue);

// Exceptions handlers
assert(series0.get(refDate) == 3);
assert(series0.get(refDate, new Exception("My exception msg")) == 3);
assert(series0.getVerbose(refDate) == 3);    
assert(series0.getExtraVerbose(refDate, "My exception msg") == 3);    

assert(collectExceptionMsg!Exception(
        series0.get(missingDate)
    ) == "Series double[Date]: Missing required key");

assert(collectExceptionMsg!Exception(
        series0.get(missingDate, new Exception("My exception msg"))
    ) == "My exception msg");

assert(collectExceptionMsg!Exception(
        series0.getVerbose(missingDate)
    ) == "Series double[Date]: Missing 2016-Mar-01 key");

assert(collectExceptionMsg!Exception(
        series0.getExtraVerbose(missingDate, "My exception msg")
    ) == "My exception msg. Series double[Date]: Missing 2016-Mar-01 key");

// assign with get*
series0.get(refDate) = 100; 
assert(series0.get(refDate) == 100); 
series0.get(refDate) = 3; 

// tryGet
double val;
assert(series0.tryGet(refDate, val));
assert(val == 3);
assert(!series0.tryGet(missingDate, val));
assert(val == 3); // val was not changed

//////////////////////////////////////
// Merges multiple series into one.
// Allocates using GC. M
// Makes exactly two allocations per merge:
// one for index/time and one for data.
//////////////////////////////////////
auto m0 = unionSeries(series0, series1);
auto m1 = unionSeries(series1, series0); // order is matter

assert(m0.index == [
    Date(2017, 01, 01),
    Date(2017, 02, 01),
    Date(2017, 03, 01),
    Date(2017, 04, 01),
    Date(2017, 05, 01)]);

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);

//////////////////////////////////////
// Joins two time-series into a one with two columns.
//////////////////////////////////////
auto u = [index0, index1].multiwayUnion;
auto index = u.move.array;
auto data = slice!double([index.length, 2], 0); // initialized to 0 value
auto series = index.series(data);

series[0 .. $, 0][].opIndexAssign(series0); // fill first column
series[0 .. $, 1][] = series1; // fill second column

assert(data == [
    [1, 10],
    [0, 20],
    [3,  0],
    [4,  0],
    [0, 50]]);
Examples:
import mir.series;

double[int] map;
map[1] = 4.0;
map[2] = 5.0;
map[4] = 6.0;
map[5] = 10.0;
map[10] = 11.0;

const s = series(map);

double value;
int key;
assert(s.tryGet(2, value) && value == 5.0);
assert(!s.tryGet(8, value));

assert(s.tryGetNext(2, value) && value == 5.0);
assert(s.tryGetPrev(2, value) && value == 5.0);
assert(s.tryGetNext(8, value) && value == 11.0);
assert(s.tryGetPrev(8, value) && value == 10.0);
assert(!s.tryGetFirst(8, 9, value));
assert(s.tryGetFirst(2, 10, value) && value == 5.0);
assert(s.tryGetLast(2, 10, value) && value == 11.0);
assert(s.tryGetLast(2, 8, value) && value == 10.0);

key = 2; assert(s.tryGetNextUpdateKey(key, value) && key == 2 && value == 5.0);
key = 2; assert(s.tryGetPrevUpdateKey(key, value) && key == 2 && value == 5.0);
key = 8; assert(s.tryGetNextUpdateKey(key, value) && key == 10 && value == 11.0);
key = 8; assert(s.tryGetPrevUpdateKey(key, value) && key == 5 && value == 10.0);
key = 2; assert(s.tryGetFirstUpdateLower(key, 10, value) && key == 2 && value == 5.0);
key = 10; assert(s.tryGetLastUpdateKey(2, key, value) && key == 10 && value == 11.0);
key = 8; assert(s.tryGetLastUpdateKey(2, key, value) && key == 5 && value == 10.0);
struct mir_observation(Index, Data);

alias Observation = mir_observation(Index, Data);
Plain index/time observation data structure. Observation are used as return tuple for for indexing Series.
Index index;
Date, date-time, time, or index.
alias time = index;
An alias for time-series index.
alias key = index;
An alias for key-value representation.
Data data;
Value or ndslice.
alias value = data;
An alias for key-value representation.
auto observation(Index, Data)(Index index, Data data);
Convenient function for Observation construction.
template SeriesMap(K, V)
Convinient alias for 1D Contiguous Series.
Examples:
import std.traits;
import mir.series;

static assert (is(SeriesMap!(string, double) == Series!(string*, double*)));

/// LHS, RHS
static assert (isAssignable!(SeriesMap!(string, double), SeriesMap!(string, double)));
static assert (isAssignable!(SeriesMap!(string, double), typeof(null)));

static assert (isAssignable!(SeriesMap!(const string, double), SeriesMap!(string, double)));
static assert (isAssignable!(SeriesMap!(string, const double), SeriesMap!(string, double)));
static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, double)));

static assert (isAssignable!(SeriesMap!(immutable string, double), SeriesMap!(immutable string, double)));
static assert (isAssignable!(SeriesMap!(immutable string, const double), SeriesMap!(immutable string, double)));
static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(immutable string, double)));
static assert (isAssignable!(SeriesMap!(string, immutable double), SeriesMap!(string, immutable double)));
static assert (isAssignable!(SeriesMap!(const string, immutable double), SeriesMap!(string, immutable double)));
static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, immutable double)));
// etc
struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous);

alias Series = mir_series(IndexIterator_, Iterator_, ulong N_ = 1, mir_slice_kind kind_ = Contiguous);
Plain index series data structure.
*.index[i]/*.key[i]/*.time corresponds to *.data[i]/*.value.
Index is assumed to be sorted. sort can be used to normalise a series.
Examples:
1-dimensional data
auto index = [1, 2, 3, 4];
auto data = [2.1, 3.4, 5.6, 7.8];
auto series = index.series(data);
const cseries = series;

assert(series.contains(2));
assert( ()@trusted{ return (2 in series) is &data[1]; }() );

assert(!series.contains(5));
assert( ()@trusted{ return (5 in series) is null; }() );

assert(series.lowerBound(2) == series[0 .. 1]);
assert(series.upperBound(2) == series[2 .. $]);

assert(cseries.lowerBound(2) == cseries[0 .. 1]);
assert(cseries.upperBound(2) == cseries[2 .. $]);

// slicing type deduction for const / immutable series
static assert(is(typeof(series[]) == 
    Series!(int*, double*)));
static assert(is(typeof(cseries[]) == 
    Series!(const(int)*, const(double)*)));
static assert(is(typeof((cast(immutable) series)[]) == 
    Series!(immutable(int)*, immutable(double)*)));

/// slicing
auto seriesSlice  = series[1 .. $ - 1];
assert(seriesSlice.index == index[1 .. $ - 1]);
assert(seriesSlice.data == data[1 .. $ - 1]);
static assert(is(typeof(series) == typeof(seriesSlice)));

/// indexing
assert(series[1] == observation(2, 3.4));

/// range primitives
assert(series.length == 4);
assert(series.front == observation(1, 2.1));

series.popFront;
assert(series.front == observation(2, 3.4));

series.popBackN(10);
assert(series.empty);
Examples:
2-dimensional data
import std.datetime: Date;
import mir.ndslice.topology: canonical, iota;

size_t row_length = 5;

auto index = [
    Date(2017, 01, 01),
    Date(2017, 02, 01),
    Date(2017, 03, 01),
    Date(2017, 04, 01)];

//  1,  2,  3,  4,  5
//  6,  7,  8,  9, 10
// 11, 12, 13, 14, 15
// 16, 17, 18, 19, 20
auto data = iota!int([index.length, row_length], 1);

// canonical and universal ndslices are more flexible then contiguous
auto series = index.series(data.canonical);

/// slicing
auto seriesSlice  = series[1 .. $ - 1, 2 .. 4];
assert(seriesSlice.index == index[1 .. $ - 1]);
assert(seriesSlice.data == data[1 .. $ - 1, 2 .. 4]);

static if (kindOf!(typeof(series.data)) != Contiguous)
    static assert(is(typeof(series) == typeof(seriesSlice)));

/// indexing
assert(series[1, 4] == observation(Date(2017, 02, 01), 10));
assert(series[2] == observation(Date(2017, 03, 01), iota!int([row_length], 11)));

/// range primitives
assert(series.length == 4);
assert(series.length!1 == 5);

series.popFront!1;
assert(series.length!1 == 4);
Examples:
Construct from null
import mir.series;
alias Map = Series!(string*, double*);
Map a = null;
auto b = Map(null);
assert(a.empty);
assert(b.empty);

auto fun(Map a = null)
{
    
}
alias IndexIterator = IndexIterator_;
alias Iterator = Iterator_;
enum size_t N;
enum SliceKind kind;
Slice!(Iterator, N, kind) _data;
IndexIterator _index;
alias Index = typeof(this.front.index);

alias Key = Index;

alias Time = Index;
Index / Key / Time type aliases
alias Data = typeof(this.front.data);

alias Value = Data;
Data / Value type aliases
alias time = index;
An alias for time-series index.
alias key = index;
An alias for key-value representation.
alias value = data;
An alias for key-value representation.
this()(Slice!IndexIterator index, Slice!(Iterator, N, kind) data);
this(typeof(null));
Construct from null
const bool opEquals(RIndexIterator, RIterator, size_t RN, SliceKind rkind)(Series!(RIndexIterator, RIterator, RN, rkind) rhs);
@property @trusted auto index()();

const @property @trusted auto index()();

immutable @property @trusted auto index()();
Index series is assumed to be sorted.
IndexIterator is an iterator on top of date, date-time, time, or numbers or user defined types with defined opCmp. For example, Date*, DateTime*, immutable(long)*, mir.ndslice.iterator.IotaIterator.
@property @trusted auto data()();

const @property @trusted auto data()();

immutable @property @trusted auto data()();
Data is any ndslice with only one constraints, data and index lengths should be equal.
typeof(this) opBinary(string op : "~")(typeof(this) rhs);

const auto opBinary(string op : "~")(const typeof(this) rhs);
Examples:
import std.datetime: Date;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [1,3,4];
auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];
auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////
// Order is matter.
// The first slice has higher priority.
auto m0 = series0 ~ series1;
auto m1 = series1 ~ series0;

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);
void opIndexAssign(IndexIterator_, Iterator_, size_t N_, SliceKind kind_)(Series!(IndexIterator_, Iterator_, N_, kind_) r);
Special [] = index-assign operator for index-series. Assigns data from r with index intersection. If a index index in r is not in the index index for this series, then no op-assign will take place. This and r series are assumed to be sorted.
Parameters:
Series!(IndexIterator_, Iterator_, N_, kind_) r rvalue index-series
Examples:
auto index = [1, 2, 3, 4];
auto data = [10.0, 10, 10, 10];
auto series = index.series(data);

auto rindex = [0, 2, 4, 5];
auto rdata = [1.0, 2, 3, 4];
auto rseries = rindex.series(rdata);

// series[] = rseries;
series[] = rseries;
assert(series.data == [10, 2, 10, 3]);
void opIndexOpAssign(string op, IndexIterator_, Iterator_, size_t N_, SliceKind kind_)(auto ref Series!(IndexIterator_, Iterator_, N_, kind_) rSeries);
Special [] op= index-op-assign operator for index-series. Op-assigns data from r with index intersection. If a index index in r is not in the index index for this series, then no op-assign will take place. This and r series are assumed to be sorted.
Parameters:
Series!(IndexIterator_, Iterator_, N_, kind_) rSeries rvalue index-series
Examples:
auto index = [1, 2, 3, 4];
auto data = [10.0, 10, 10, 10];
auto series = index.series(data);

auto rindex = [0, 2, 4, 5];
auto rdata = [1.0, 2, 3, 4];
auto rseries = rindex.series(rdata);

series[] += rseries;
assert(series.data == [10, 12, 10, 13]);
auto lowerBound(Index)(auto ref scope const Index key);

const auto lowerBound(Index)(auto ref scope const Index key);
This function uses a search with policy sp to find the largest left subrange on which t < key is true for all t. The search schedule and its complexity are documented in std.range.SearchPolicy.
auto upperBound(Index)(auto ref scope const Index key);

const auto upperBound(Index)(auto ref scope const Index key);
This function uses a search with policy sp to find the largest left subrange on which t > key is true for all t. The search schedule and its complexity are documented in std.range.SearchPolicy.
ref @trusted auto get(Index, Value)(auto ref scope const Index key, return ref Value _default)
if (!is(Value : const(Exception)));

const ref auto get(Index, Value)(auto ref scope const Index key, return ref Value _default)
if (!is(Value : const(Exception)));

immutable ref auto get(Index, Value)(auto ref scope const Index key, return ref Value _default)
if (!is(Value : const(Exception)));

const auto get(Index, Value)(auto ref scope const Index key, Value _default)
if (!is(Value : const(Exception)));

immutable auto get(Index, Value)(auto ref scope const Index key, Value _default)
if (!is(Value : const(Exception)));
Gets data for the index.
Parameters:
Index key index
Value _default default value is returned if the series does not contains the index.
Returns:
data that corresponds to the index or default value.
ref @trusted auto get(Index)(auto ref scope const Index key);

ref @trusted auto get(Index)(auto ref scope const Index key, lazy const Exception exc);

const ref auto get(Index)(auto ref scope const Index key);

const ref auto get(Index)(auto ref scope const Index key, lazy const Exception exc);

immutable ref auto get(Index)(auto ref scope const Index key);

immutable ref auto get(Index)(auto ref scope const Index key, lazy const Exception exc);
Gets data for the index.
Parameters:
Index key index
Exception exc (lazy, optional) exception to throw if the series does not contains the index.
Returns:
data that corresponds to the index.
Throws:
Exception if the series does not contains the index.
ref auto getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__);

const ref auto getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__);

immutable ref auto getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__);
Gets data for the index (verbose exception).
Parameters:
Index key index
Returns:
data that corresponds to the index.
Throws:
Detailed exception if the series does not contains the index.
ref auto getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__);

const ref auto getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__);

immutable ref auto getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__);
Gets data for the index (extra verbose exception).
Parameters:
Index key index
Returns:
data that corresponds to the index.
Throws:
Detailed exception if the series does not contains the index.
const @trusted bool contains(Index)(auto ref scope const Index key);
@trusted auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key);

const auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key);

immutable auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key);
@trusted bool tryGet(Index, Value)(Index key, ref scope Value val);

const bool tryGet(Index, Value)(Index key, ref scope Value val);

immutable bool tryGet(Index, Value)(Index key, ref scope Value val);
Tries to get the first value, such that key_i == key.
Returns:
true on success.
bool tryGetNext(Index, Value)(auto ref scope const Index key, ref scope Value val);

const bool tryGetNext(Index, Value)(auto ref scope const Index key, ref scope Value val);

immutable bool tryGetNext(Index, Value)(auto ref scope const Index key, ref scope Value val);
Tries to get the first value, such that key_i >= key.
Returns:
true on success.
@trusted bool tryGetNextUpdateKey(Index, Value)(ref scope Index key, ref scope Value val);

const bool tryGetNextUpdateKey(Index, Value)(ref scope Index key, ref scope Value val);

immutable bool tryGetNextUpdateKey(Index, Value)(ref scope Index key, ref scope Value val);
Tries to get the first value, such that key_i >= key. Updates key with key_i.
Returns:
true on success.
bool tryGetPrev(Index, Value)(auto ref scope const Index key, ref scope Value val);

const bool tryGetPrev(Index, Value)(auto ref scope const Index key, ref scope Value val);

immutable bool tryGetPrev(Index, Value)(auto ref scope const Index key, ref scope Value val);
Tries to get the last value, such that key_i <= key.
Returns:
true on success.
@trusted bool tryGetPrevUpdateKey(Index, Value)(ref scope Index key, ref scope Value val);

const bool tryGetPrevUpdateKey(Index, Value)(ref scope Index key, ref scope Value val);

immutable bool tryGetPrevUpdateKey(Index, Value)(ref scope Index key, ref scope Value val);
Tries to get the last value, such that key_i <= key. Updates key with key_i.
Returns:
true on success.
@trusted bool tryGetFirst(Index, Value)(auto ref scope const Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);

const bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);

immutable bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);
Tries to get the first value, such that lowerBound <= key_i <= upperBound.
Returns:
true on success.
@trusted bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);

const bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);

immutable bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);
Tries to get the first value, such that lowerBound <= key_i <= upperBound. Updates lowerBound with key_i.
Returns:
true on success.
@trusted bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);

const bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);

immutable bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, ref scope Value val);
Tries to get the last value, such that lowerBound <= key_i <= upperBound.
Returns:
true on success.
@trusted bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, ref scope Value val);

const bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, ref scope Value val);

immutable bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, ref scope Value val);
Tries to get the last value, such that lowerBound <= key_i <= upperBound. Updates upperBound with key_i.
Returns:
true on success.
@property auto asSlice()();

const @property auto asSlice()();

immutable @property auto asSlice()();
Returns:
1D Slice with creared with zip  ([0] - key, [1] - value).
See Also:
map  uses multiargument lambdas to handle zipped slices.
const @property bool empty(size_t dimension = 0)()
if (dimension < N);

const @property size_t length(size_t dimension = 0)()
if (dimension < N);

@property auto front(size_t dimension = 0)()
if (dimension < N);

@property auto back(size_t dimension = 0)()
if (dimension < N);

@trusted void popFront(size_t dimension = 0)()
if (dimension < N);

void popBack(size_t dimension = 0)()
if (dimension < N);

@trusted void popFrontExactly(size_t dimension = 0)(size_t n)
if (dimension < N);

void popBackExactly(size_t dimension = 0)(size_t n)
if (dimension < N);

void popFrontN(size_t dimension = 0)(size_t n)
if (dimension < N);

void popBackN(size_t dimension = 0)(size_t n)
if (dimension < N);

const Slice!(IotaIterator!size_t) opSlice(size_t dimension = 0)(size_t i, size_t j)
if (dimension < N);

const size_t opDollar(size_t dimension = 0)();

auto opIndex(Slices...)(Slices slices)
if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices));

const auto opIndex(Slices...)(Slices slices)
if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices));

immutable auto opIndex(Slices...)(Slices slices)
if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices));
ndslice-like primitives
ref @trusted auto opAssign(typeof(this) rvalue) return;

ref auto opAssign(RIndexIterator, RIterator)(Series!(RIndexIterator, RIterator, N, kind) rvalue) return
if (isAssignable!(IndexIterator, RIndexIterator) && isAssignable!(Iterator, RIterator));

ref auto opAssign(RIndexIterator, RIterator)(auto ref const Series!(RIndexIterator, RIterator, N, kind) rvalue) return
if (isAssignable!(IndexIterator, LightConstOf!RIndexIterator) && isAssignable!(Iterator, LightConstOf!RIterator));

ref auto opAssign(RIndexIterator, RIterator)(auto ref immutable Series!(RIndexIterator, RIterator, N, kind) rvalue) return
if (isAssignable!(IndexIterator, LightImmutableOf!RIndexIterator) && isAssignable!(Iterator, LightImmutableOf!RIterator));

ref auto opAssign(typeof(null)) return;

@property auto save()();
@property scope @trusted Series!(LightScopeOf!IndexIterator, LightScopeOf!Iterator, N, kind) lightScope()() return;

const @property scope @trusted Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return;

immutable @property scope @trusted Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return;
const @property scope @trusted Series!(LightConstOf!IndexIterator, LightConstOf!Iterator, N, kind) lightConst()() return;
immutable @property scope @trusted Series!(LightImmutableOf!IndexIterator, LightImmutableOf!Iterator, N, kind) lightImmutable()() return;
const @property auto toConst()();
const void toString(Writer, Spec)(auto ref Writer w, ref const Spec f);
Examples:
import mir.series: series, sort;
auto s = ["b", "a"].series([9, 8]).sort;

import std.conv : to;
assert(s.to!string == `["a":8, "b":9]`);

import std.format : format;
assert("%s".format(s) == `["a":8, "b":9]`);
assert("%(%s %s | %)".format(s) == `"a" 8 | "b" 9`);
assert("%-(%s,%s\n%)\n".format(s) == "a,8\nb,9\n");
auto series(IndexIterator, Iterator, size_t N, SliceKind kind)(Slice!IndexIterator index, Slice!(Iterator, N, kind) data);

auto series(Index, Data)(Index[] index, Data[] data);

auto series(IndexIterator, Data)(Slice!IndexIterator index, Data[] data);

auto series(Index, Iterator, size_t N, SliceKind kind)(Index[] index, Slice!(Iterator, N, kind) data);
Convenient function for Series construction.
See Also:

Attention This overloads do not sort the data. User should call directly if index was not sorted.

Series!(K*, V*) series(RK, RV, K = RK, V = RV)(RV[RK] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

Series!(RK*, RV*) series(K, V, RK = const(K), RV = const(V))(const V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

Series!(RK*, RV*) series(K, V, RK = immutable(K), RV = immutable(V))(immutable V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

auto series(K, V)(V[K]* aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));
Constructs a GC-allocated series from an associative array. Performs exactly two allocations.
Parameters:
RV[RK] aa associative array or a pointer to associative array
Returns:
sorted GC-allocated series.
See Also:
Examples:
auto s = [1: 1.5, 3: 3.3, 2: 20.9].series;
assert(s.index == [1, 2, 3]);
assert(s.data == [1.5, 20.9, 3.3]);
assert(s.data[s.findIndex(2)] == 20.9);
auto rcseries(RK, RV, K = RK, V = RV)(RV[RK] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

auto rcseries(K, V, RK = const(K), RV = const(V))(const V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

auto rcseries(K, V, RK = immutable(K), RV = immutable(V))(immutable V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

auto rcseries(K, V)(V[K]* aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));
Constructs a RC-allocated series from an associative array. Performs exactly two allocations.
Parameters:
RV[RK] aa associative array or a pointer to associative array
Returns:
sorted RC-allocated series.
See Also:
Examples:
auto s = [1: 1.5, 3: 3.3, 2: 20.9].rcseries;
assert(s.index == [1, 2, 3]);
assert(s.data == [1.5, 20.9, 3.3]);
assert(s.data[s.findIndex(2)] == 20.9);
Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K]* aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));
Constructs a manually allocated series from an associative array. Performs exactly two allocations.
Parameters:
V[K] aa = associative array or a pointer to associative array
Returns:
sorted manually allocated series.
Examples:
import std.experimental.allocator;
import std.experimental.allocator.building_blocks.region;

InSituRegion!(1024) allocator;
auto aa = [1: 1.5, 3: 3.3, 2: 2.9];

auto s = (double[int] aa) @nogc @trusted pure nothrow {
    return allocator.makeSeries(aa);
}(aa);

auto indexArray = s.index.field;
auto dataArray = s.data.field;

assert(s.index == [1, 2, 3]);
assert(s.data == [1.5, 2.9, 3.3]);
assert(s.data[s.findIndex(2)] == 2.9);

allocator.dispose(indexArray);
allocator.dispose(dataArray);
auto assocArray(IndexIterator, Iterator, size_t N, SliceKind kind)(Series!(IndexIterator, Iterator, N, kind) series);
Returns a newly allocated associative array from a range of key/value tuples.
Parameters:
Series!(IndexIterator, Iterator, N, kind) series index / time Series, may not be sorted
Returns:
A newly allocated associative array out of elements of the input series. Returns a null associative array reference when given an empty series.

Duplicates Associative arrays have unique keys. If r contains duplicate keys, then the result will contain the value of the last pair for that key in r.

Examples:
import mir.ndslice; //iota and etc
import mir.series;

auto s = ["c", "a", "b"].series(3.iota!int);
assert(s.assocArray == [
    "c": 0,
    "a": 1,
    "b": 2,
]);
enum auto isSeries(U);
Returns:
true if U is a Series;
size_t findIndex(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key);
Finds an index such that series.index[index] == key.
Parameters:
Series!(IndexIterator, Iterator, N, kind) series series
Index key index to find in the series
Returns:
size_t.max if the series does not contain the key and appropriate index otherwise.
Examples:
auto index = [1, 2, 3, 4].sliced;
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
auto series = index.series(data);

assert(series.data[series.findIndex(3)] == 5.6);
assert(series.findIndex(0) == size_t.max);
size_t find(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key);
Finds a backward index such that series.index[$ - backward_index] == key.
Parameters:
Series!(IndexIterator, Iterator, N, kind) series series
Index key index key to find in the series
Returns:
0 if the series does not contain the key and appropriate backward index otherwise.
Examples:
auto index = [1, 2, 3, 4].sliced;
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
auto series = index.series(data);

if (auto bi = series.find(3))
{
    assert(series.data[$ - bi] == 5.6);
}
else
{
    assert(0);
}

assert(series.find(0) == 0);
template troykaGalop(alias lfun, alias cfun, alias rfun)
Iterates union using three functions to handle each intersection case separately.
Parameters:
lfun binary function that accepts left side key (and left side value)
cfun trinary function that accepts left side key, (left side value,) and right side value
rfun binary function that accepts right side key (and right side value)
void troykaGalop(IndexIterL, IterL, size_t LN, SliceKind lkind, IndexIterR, IterR, size_t RN, SliceKind rkind)(Series!(IndexIterL, IterL, LN, lkind) lhs, Series!(IndexIterR, IterR, RN, rkind) rhs);
Parameters:
Series!(IndexIterL, IterL, LN, lkind) lhs left hand series
Series!(IndexIterR, IterR, RN, rkind) rhs right hand series
void troykaGalop(LeftRange, RightRange)(LeftRange lhs, RightRange rhs)
if (isInputRange!LeftRange && isInputRange!RightRange && !isSeries!LeftRange && !isSeries!RightRange);
Parameters:
LeftRange lhs left hand input range
RightRange rhs right hand input range
template troykaSeries(alias lfun, alias cfun, alias rfun)
Constructs union using three functions to handle each intersection case separately.
Parameters:
lfun binary function that accepts left side key and left side value
cfun trinary function that accepts left side key, left side value, and right side value
rfun binary function that accepts right side key and right side value
Examples:
import mir.ndslice;
auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1));
auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0);
alias unionAlgorithm = troykaSeries!(
    (key, left) => left,
    (key, left, right) => left + right,
    (key, right) => -right,
);
auto c = unionAlgorithm(a, b);
assert(c.index == [0, 1, 2, 3, 4, 9]);
assert(c.data == [-10, 1, 22, 3, -30, 44]);
auto troykaSeries(IndexIterL, IterL, size_t LN, SliceKind lkind, IndexIterR, IterR, size_t RN, SliceKind rkind)(Series!(IndexIterL, IterL, LN, lkind) lhs, Series!(IndexIterR, IterR, RN, rkind) rhs);
Parameters:
Series!(IndexIterL, IterL, LN, lkind) lhs left hand series
Series!(IndexIterR, IterR, RN, rkind) rhs right hand series
Returns:
GC-allocated union series with length equal to troykaLength
template rcTroykaSeries(alias lfun, alias cfun, alias rfun)
Constructs union using three functions to handle each intersection case separately.
Parameters:
lfun binary function that accepts left side key and left side value
cfun trinary function that accepts left side key, left side value, and right side value
rfun binary function that accepts right side key and right side value
Examples:
import mir.ndslice;
auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1));
auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0);
alias unionAlgorithm = rcTroykaSeries!(
    (key, left) => left,
    (key, left, right) => left + right,
    (key, right) => -right,
);
auto c = unionAlgorithm(a, b);
assert(c.index == [0, 1, 2, 3, 4, 9]);
assert(c.data == [-10, 1, 22, 3, -30, 44]);
auto rcTroykaSeries(IndexIterL, IterL, size_t LN, SliceKind lkind, IndexIterR, IterR, size_t RN, SliceKind rkind)(auto ref Series!(IndexIterL, IterL, LN, lkind) lhs, auto ref Series!(IndexIterR, IterR, RN, rkind) rhs);
Parameters:
Series!(IndexIterL, IterL, LN, lkind) lhs left hand series
Series!(IndexIterR, IterR, RN, rkind) rhs right hand series
Returns:
RC-allocated union series with length equal to troykaLength
size_t troykaLength(IndexIterL, IterL, size_t LN, SliceKind lkind, IndexIterR, IterR, size_t RN, SliceKind rkind)(Series!(IndexIterL, IterL, LN, lkind) lhs, Series!(IndexIterR, IterR, RN, rkind) rhs);

size_t troykaLength(LeftRange, RightRange)(LeftRange lhs, RightRange rhs)
if (!isSeries!LeftRange && !isSeries!RightRange);
Length for Troyka union handlers.
Parameters:
Series!(IndexIterL, IterL, LN, lkind) lhs left hand side series/range
Series!(IndexIterR, IterR, RN, rkind) rhs right hand side series/range
Returns:
Total count of lambda function calls in troykaGalop union handler.
template troykaSeriesImpl(alias lfun, alias cfun, alias rfun)
void troykaSeriesImpl(I, E, IndexIterL, IterL, size_t LN, SliceKind lkind, IndexIterR, IterR, size_t RN, SliceKind rkind, UI, UE)(Series!(IndexIterL, IterL, LN, lkind) lhs, Series!(IndexIterR, IterR, RN, rkind) rhs, Series!(UI*, UE*) uninitSlice);
auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...)
if (C > 1);
Merges multiple (time) series into one. Makes exactly one memory allocation for two series union and two memory allocation for three and more series union.
Parameters:
Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple variadic static array of composed of series, each series must be sorted.
Returns:
sorted GC-allocated series. See_also Series.opBinary makeUnionSeries
Examples:
import std.datetime: Date;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [1,3,4];
auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];
auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////
// Order is matter.
// The first slice has higher priority.
auto m0 = unionSeries(series0, series1);
auto m1 = unionSeries(series1, series0);

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);
Examples:
import std.datetime: Date;

//////////////////////////////////////
// Constructs three time-series.
//////////////////////////////////////
auto index0 = [1,3,4];
auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];
auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

auto index2 = [1, 6];
auto data2 = [100.0, 600];
auto series2 = index2.series(data2);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////
// Order is matter.
// The first slice has higher priority.
auto m0 = unionSeries(series0, series1, series2);
auto m1 = unionSeries(series1, series0, series2);
auto m2 = unionSeries(series2, series0, series1);

assert(m0.index == m1.index);
assert(m0.index == m2.index);
assert(m0.data == [  1, 20,  3,  4, 50, 600]);
assert(m1.data == [ 10, 20,  3,  4, 50, 600]);
assert(m2.data == [100, 20,  3,  4, 50, 600]);
auto makeUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator)(auto ref Allocator allocator, Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...)
if (C > 1);
Merges multiple (time) series into one.
Parameters:
Allocator allocator memory allocator
Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple variadic static array of composed of series.
Returns:
sorted manually allocated series. See_also unionSeries
Examples:
import std.experimental.allocator;
import std.experimental.allocator.building_blocks.region;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [1,3,4];

auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];

auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////

InSituRegion!(1024) allocator;

auto m0 = allocator.makeUnionSeries(series0, series1);
auto m1 = allocator.makeUnionSeries(series1, series0); // order is matter

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);

/// series should have the same sizes as after allocation
allocator.dispose(m0.index.field);
allocator.dispose(m0.data.field);
allocator.dispose(m1.index.field);
allocator.dispose(m1.data.field);
auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...)
if (C > 1);
Merges multiple (time) series into one.
Parameters:
Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple variadic static array of composed of series.
Returns:
sorted manually allocated series. See_also unionSeries
Examples:
import mir.rc.array;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [1,3,4];

auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];

auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////

Series!(RCI!int, RCI!double) m0 = rcUnionSeries(series0, series1);
Series!(RCI!int, RCI!double) m1 = rcUnionSeries(series1, series0); // order is matter

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);
auto unionSeriesImpl(I, E, IndexIterator, Iterator, size_t N, SliceKind kind, UI, UE)(Series!(IndexIterator, Iterator, N, kind)[] seriesTuple, Series!(UI*, UE*, N) uninitSeries);
Initialize preallocated series using union of multiple (time) series. Doesn't make any allocations.
Parameters:
Series!(IndexIterator, Iterator, N, kind)[] seriesTuple dynamic array composed of series.
Series!(UI*, UE*, N) uninitSeries uninitialized series with exactly required length.
@property ref V[K] insertOrAssign(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return ref V[K] aa, auto ref Series!(IndexIterator, Iterator, N, kind) series);
Inserts or assigns a series to the associative array aa.
Parameters:
V[K] aa associative array
Series!(IndexIterator, Iterator, N, kind) series series
Returns:
associative array
Examples:
auto a = [1: 3.0, 4: 2.0];
auto s = series([1, 2, 3], [10, 20, 30]);
a.insertOrAssign = s;
assert(a.series == series([1, 2, 3, 4], [10.0, 20, 30, 2]));
@property ref V[K] insert(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return ref V[K] aa, auto ref Series!(IndexIterator, Iterator, N, kind) series);
Inserts a series to the associative array aa.
Parameters:
V[K] aa associative array
Series!(IndexIterator, Iterator, N, kind) series series
Returns:
associative array
Examples:
auto a = [1: 3.0, 4: 2.0];
auto s = series([1, 2, 3], [10, 20, 30]);
a.insert = s;
assert(a.series == series([1, 2, 3, 4], [3.0, 20, 30, 2]));