Skip to content

Commit bb9fe40

Browse files
committed
extend timeseries api and add constructor
1 parent fc5e701 commit bb9fe40

File tree

1 file changed

+86
-34
lines changed

1 file changed

+86
-34
lines changed

source/mir/timeseries.d

Lines changed: 86 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,50 @@ Plain time series data structure.
9191
+/
9292
struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
9393
{
94+
import std.range: SearchPolicy, assumeSorted;
95+
96+
@optmath:
97+
98+
///
99+
Slice!(kind, packs, Iterator) _data;
100+
101+
///
102+
TimeIterator _time;
103+
104+
///
105+
this()(Slice!(Contiguous, [1], TimeIterator) time, Slice!(kind, packs, Iterator) data)
106+
{
107+
assert(time.length == data.length, "Series constructor: time and data lengths must be equal.");
108+
_data = data;
109+
_time = time._iterator;
110+
111+
}
112+
113+
///
114+
bool opEquals()(typeof(this) rhs)
115+
{
116+
return this.time == rhs.time && this.data == rhs.data;
117+
}
118+
94119
/++
95120
Time series is assumed to be sorted.
96121
97122
`TimeIterator` is an iterator on top of date, date-time, time, or integer types.
98123
For example, `Date*`, `DateTime*`, `immutable(long)*`, `mir.ndslice.iterator.IotaIterator`.
99124
+/
100-
Slice!(Contiguous, [1], TimeIterator) time;
125+
Slice!(Contiguous, [1], TimeIterator) time() @property @trusted
126+
{
127+
return _time.sliced(_data._lengths[0]);
128+
}
101129

102130
/++
103131
Data is any ndslice with only one constraints,
104132
`data` and `time` lengths should be equal.
105133
+/
106-
Slice!(kind, packs, Iterator) data;
107-
108-
@optmath:
134+
Slice!(kind, packs, Iterator) data() @property @trusted
135+
{
136+
return _data;
137+
}
109138

110139
/++
111140
Special `[] =` index-assign operator for time-series.
@@ -133,7 +162,8 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
133162
auto rdata = [1.0, 2, 3, 4].sliced;
134163
auto rseries = rtime.series(rdata);
135164

136-
series[] = rseries;
165+
// series[] = rseries;
166+
series.opIndexAssign(rseries);
137167
assert(series.data == [10, 2, 10, 3]);
138168
}
139169

@@ -149,9 +179,8 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
149179
void opIndexOpAssign(string op, TimeIterator_, SliceKind kind_, size_t[] packs_, Iterator_)
150180
(Series!(TimeIterator_, kind_, packs_, Iterator_) r)
151181
{
182+
import std.traits: Unqual;
152183
auto l = this;
153-
assert(r.data.length == r.time.length);
154-
assert(l.data.length == l.time.length);
155184
if (r.empty)
156185
return;
157186
if (l.empty)
@@ -218,18 +247,14 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
218247
bool empty(size_t dimension = 0)() const @property
219248
if (dimension < packs[0])
220249
{
221-
static if (!dimension)
222-
assert(data.length == time.length);
223250
return !length!dimension;
224251
}
225252

226253
/// ditto
227254
size_t length(size_t dimension = 0)() const @property
228255
if (dimension < packs[0])
229256
{
230-
static if (!dimension)
231-
assert(data.length == time.length);
232-
return data.length!dimension;
257+
return _data.length!dimension;
233258
}
234259

235260
/// ditto
@@ -254,52 +279,48 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
254279
assert(!empty!dimension);
255280
static if (dimension)
256281
{
257-
return time.series(data.back!dimension);
282+
return time.series(_data.back!dimension);
258283
}
259284
else
260285
{
261-
return time.back.observation(data.back);
286+
return time.back.observation(_data.back);
262287
}
263288
}
264289

265290
/// ditto
266-
void popFront(size_t dimension = 0)()
291+
void popFront(size_t dimension = 0)() @trusted
267292
if (dimension < packs[0])
268293
{
269294
assert(!empty!dimension);
270-
static if (!dimension)
271-
time.popFront;
272-
data.popFront!dimension;
295+
static if (dimension == 0)
296+
_time++;
297+
_data.popFront!dimension;
273298
}
274299

275300
/// ditto
276301
void popBack(size_t dimension = 0)()
277302
if (dimension < packs[0])
278303
{
279304
assert(!empty!dimension);
280-
static if (!dimension)
281-
time.popBack;
282-
data.popBack!dimension;
305+
_data.popBack!dimension;
283306
}
284307

285308
/// ditto
286-
void popFrontExactly(size_t dimension = 0)(size_t n)
309+
void popFrontExactly(size_t dimension = 0)(size_t n) @trusted
287310
if (dimension < packs[0])
288311
{
289312
assert(length!dimension >= n);
290-
static if (!dimension)
291-
time.popFrontExactly(n);
292-
data.popFrontExactly!dimension(n);
313+
static if (dimension == 0)
314+
_time += n;
315+
_data.popFrontExactly!dimension(n);
293316
}
294317

295318
/// ditto
296319
void popBackExactly(size_t dimension = 0)(size_t n)
297320
if (dimension < packs[0])
298321
{
299322
assert(length!dimension >= n);
300-
static if (!dimension)
301-
time.popBackExactly(n);
302-
data.popBackExactly!dimension(n);
323+
_data.popBackExactly!dimension(n);
303324
}
304325

305326
/// ditto
@@ -329,7 +350,7 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
329350
"Series.opSlice!" ~ dimension.stringof ~ ": the left bound must be less than or equal to the right bound.");
330351
enum errorMsg = ": difference between the right and the left bounds"
331352
~ " must be less than or equal to the length of the given dimension.";
332-
assert(j - i <= data._lengths[dimension],
353+
assert(j - i <= _data._lengths[dimension],
333354
"Series.opSlice!" ~ dimension.stringof ~ errorMsg);
334355
}
335356
body
@@ -340,7 +361,7 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
340361
/// ditto
341362
size_t opDollar(size_t dimension = 0)() const
342363
{
343-
return data.opDollar!dimension;
364+
return _data.opDollar!dimension;
344365
}
345366

346367
/// ditto
@@ -357,20 +378,51 @@ struct Series(TimeIterator, SliceKind kind, size_t[] packs, Iterator)
357378
}
358379
}
359380

381+
/++
382+
This function uses a search with policy sp to find the largest left subrange on which
383+
`t < moment` is true for all `t`.
384+
The search schedule and its complexity are documented in `std.range.SearchPolicy`.
385+
+/
386+
auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, Time)(Time moment)
387+
{
388+
return this[0 .. time.assumeSorted.lowerBound!sp(moment).length];
389+
}
390+
391+
/++
392+
This function uses a search with policy sp to find the largest left subrange on which
393+
`t > moment` is true for all `t`.
394+
The search schedule and its complexity are documented in `std.range.SearchPolicy`.
395+
+/
396+
auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, Time)(Time moment)
397+
{
398+
return this[$ - time.assumeSorted.upperBound!sp(moment).length .. $];
399+
}
400+
360401
/// ditto
361402
auto save()() @property
362403
{
363404
return this;
364405
}
406+
407+
bool contains(Time)(Time momemt)
408+
{
409+
return time.assumeSorted.contains(momemt);
410+
}
365411
}
366412

367413
/// 1-dimensional data
368-
version(mir_test) unittest
414+
@safe pure version(mir_test) unittest
369415
{
370416
auto time = [1, 2, 3, 4].sliced;
371417
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
372418
auto series = time.series(data);
373419

420+
assert(series.contains(2));
421+
assert(!series.contains(5));
422+
423+
assert(series.lowerBound(2) == series[0 .. 1]);
424+
assert(series.upperBound(2) == series[2 .. $]);
425+
374426
/// slicing
375427
auto seriesSlice = series[1 .. $ - 1];
376428
assert(seriesSlice.time == time[1 .. $ - 1]);
@@ -392,7 +444,7 @@ version(mir_test) unittest
392444
}
393445

394446
/// 2-dimensional data
395-
version(mir_test) unittest
447+
@safe pure version(mir_test) unittest
396448
{
397449
import std.datetime: Date;
398450
import mir.ndslice.topology: canonical, iota;
@@ -521,7 +573,7 @@ template sort(alias less = "a < b")
521573
}
522574

523575
/// 1D data
524-
version(mir_test) unittest
576+
pure version(mir_test) unittest
525577
{
526578
auto time = [1, 2, 4, 3].sliced;
527579
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
@@ -535,7 +587,7 @@ version(mir_test) unittest
535587
}
536588

537589
/// 2D data
538-
version(mir_test) unittest
590+
pure version(mir_test) unittest
539591
{
540592
import mir.timeseries;
541593
import mir.ndslice.allocation: uninitSlice;

0 commit comments

Comments
 (0)