@@ -91,21 +91,50 @@ Plain time series data structure.
9191+/
9292struct 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