1 /++ 2 $(H1 Index-series) 3 4 The module contains $(LREF Series) data structure with special iteration and indexing methods. 5 It is aimed to construct index or time-series using Mir and Phobos algorithms. 6 7 Public_imports: $(MREF mir,ndslice,slice). 8 9 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments 10 Authors: Ilia Ki 11 12 Macros: 13 NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 14 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 15 +/ 16 module mir.series; 17 18 import mir.ndslice.iterator: IotaIterator; 19 import mir.ndslice.sorting: transitionIndex; 20 import mir.qualifier; 21 import mir.serde: serdeIgnore, serdeFields; 22 import std.traits; 23 /// 24 public import mir.ndslice.slice; 25 /// 26 public import mir.ndslice.sorting: sort; 27 28 /++ 29 See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). 30 +/ 31 @safe version(mir_test) unittest 32 { 33 import mir.test; 34 import mir.ndslice; 35 import mir.series; 36 37 import mir.array.allocation: array; 38 import mir.algorithm.setops: multiwayUnion; 39 40 import mir.date: Date; 41 import core.lifetime: move; 42 import std.exception: collectExceptionMsg; 43 44 ////////////////////////////////////// 45 // Constructs two time-series. 46 ////////////////////////////////////// 47 auto index0 = [ 48 Date(2017, 01, 01), 49 Date(2017, 03, 01), 50 Date(2017, 04, 01)]; 51 52 auto data0 = [1.0, 3, 4]; 53 auto series0 = index0.series(data0); 54 55 auto index1 = [ 56 Date(2017, 01, 01), 57 Date(2017, 02, 01), 58 Date(2017, 05, 01)]; 59 60 auto data1 = [10.0, 20, 50]; 61 auto series1 = index1.series(data1); 62 63 ////////////////////////////////////// 64 // asSlice method 65 ////////////////////////////////////// 66 assert(series0 67 .asSlice 68 // ref qualifier is optional 69 .map!((ref key, ref value) => key.yearMonthDay.month == value) 70 .all); 71 72 ////////////////////////////////////// 73 // get* methods 74 ////////////////////////////////////// 75 76 auto refDate = Date(2017, 03, 01); 77 auto missingDate = Date(2016, 03, 01); 78 79 // default value 80 double defaultValue = 100; 81 assert(series0.get(refDate, defaultValue) == 3); 82 assert(series0.get(missingDate, defaultValue) == defaultValue); 83 84 // Exceptions handlers 85 assert(series0.get(refDate) == 3); 86 assert(series0.get(refDate, new Exception("My exception msg")) == 3); 87 assert(series0.getVerbose(refDate) == 3); 88 assert(series0.getExtraVerbose(refDate, "My exception msg") == 3); 89 90 collectExceptionMsg!Exception(series0.get(missingDate)).should 91 == "Series double[Date]: Missing required key"; 92 93 collectExceptionMsg!Exception(series0.get(missingDate, new Exception("My exception msg"))).should 94 == "My exception msg"; 95 96 collectExceptionMsg!Exception(series0.getVerbose(missingDate)).should 97 == "Series double[Date]: Missing 2016-03-01 key"; 98 99 collectExceptionMsg!Exception(series0.getExtraVerbose(missingDate, "My exception msg")).should 100 == "My exception msg. Series double[Date]: Missing 2016-03-01 key"; 101 102 // assign with get* 103 series0.get(refDate) = 100; 104 assert(series0.get(refDate) == 100); 105 series0.get(refDate) = 3; 106 107 // tryGet 108 double val; 109 assert(series0.tryGet(refDate, val)); 110 assert(val == 3); 111 assert(!series0.tryGet(missingDate, val)); 112 assert(val == 3); // val was not changed 113 114 ////////////////////////////////////// 115 // Merges multiple series into one. 116 // Allocates using GC. M 117 // Makes exactly two allocations per merge: 118 // one for index/time and one for data. 119 ////////////////////////////////////// 120 auto m0 = unionSeries(series0, series1); 121 auto m1 = unionSeries(series1, series0); // order is matter 122 123 assert(m0.index == [ 124 Date(2017, 01, 01), 125 Date(2017, 02, 01), 126 Date(2017, 03, 01), 127 Date(2017, 04, 01), 128 Date(2017, 05, 01)]); 129 130 assert(m0.index == m1.index); 131 assert(m0.data == [ 1, 20, 3, 4, 50]); 132 assert(m1.data == [10, 20, 3, 4, 50]); 133 134 ////////////////////////////////////// 135 // Joins two time-series into a one with two columns. 136 ////////////////////////////////////// 137 auto u = [index0, index1].multiwayUnion; 138 auto index = u.move.array; 139 auto data = slice!double([index.length, 2], 0); // initialized to 0 value 140 auto series = index.series(data); 141 142 series[0 .. $, 0][] = series0; // fill first column 143 series[0 .. $, 1][] = series1; // fill second column 144 145 assert(data == [ 146 [1, 10], 147 [0, 20], 148 [3, 0], 149 [4, 0], 150 [0, 50]]); 151 } 152 153 /// 154 version(mir_test) 155 unittest{ 156 157 import mir.series; 158 159 double[int] map; 160 map[1] = 4.0; 161 map[2] = 5.0; 162 map[4] = 6.0; 163 map[5] = 10.0; 164 map[10] = 11.0; 165 166 const s = series(map); 167 168 double value; 169 int key; 170 assert(s.tryGet(2, value) && value == 5.0); 171 assert(!s.tryGet(8, value)); 172 173 assert(s.tryGetNext(2, value) && value == 5.0); 174 assert(s.tryGetPrev(2, value) && value == 5.0); 175 assert(s.tryGetNext(8, value) && value == 11.0); 176 assert(s.tryGetPrev(8, value) && value == 10.0); 177 assert(!s.tryGetFirst(8, 9, value)); 178 assert(s.tryGetFirst(2, 10, value) && value == 5.0); 179 assert(s.tryGetLast(2, 10, value) && value == 11.0); 180 assert(s.tryGetLast(2, 8, value) && value == 10.0); 181 182 key = 2; assert(s.tryGetNextUpdateKey(key, value) && key == 2 && value == 5.0); 183 key = 2; assert(s.tryGetPrevUpdateKey(key, value) && key == 2 && value == 5.0); 184 key = 8; assert(s.tryGetNextUpdateKey(key, value) && key == 10 && value == 11.0); 185 key = 8; assert(s.tryGetPrevUpdateKey(key, value) && key == 5 && value == 10.0); 186 key = 2; assert(s.tryGetFirstUpdateLower(key, 10, value) && key == 2 && value == 5.0); 187 key = 10; assert(s.tryGetLastUpdateKey(2, key, value) && key == 10 && value == 11.0); 188 key = 8; assert(s.tryGetLastUpdateKey(2, key, value) && key == 5 && value == 10.0); 189 } 190 191 import mir.ndslice.slice; 192 import mir.ndslice.internal: is_Slice, isIndex; 193 import mir.math.common: fmamath; 194 195 import std.meta; 196 197 @fmamath: 198 199 /++ 200 Plain index/time observation data structure. 201 Observation are used as return tuple for for indexing $(LREF Series). 202 +/ 203 struct mir_observation(Index, Data) 204 { 205 /// Date, date-time, time, or index. 206 Index index; 207 /// Value or ndslice. 208 Data data; 209 } 210 211 /// ditto 212 alias Observation = mir_observation; 213 214 /// Convenient function for $(LREF Observation) construction. 215 auto observation(Index, Data)(Index index, Data data) 216 { 217 alias R = mir_observation!(Index, Data); 218 return R(index, data); 219 } 220 221 /++ 222 Convinient alias for 1D Contiguous $(LREF Series). 223 +/ 224 alias SeriesMap(K, V) = mir_series!(K*, V*); 225 226 /// 227 version(mir_test) unittest 228 { 229 import std.traits; 230 import mir.series; 231 232 static assert (is(SeriesMap!(string, double) == Series!(string*, double*))); 233 234 /// LHS, RHS 235 static assert (isAssignable!(SeriesMap!(string, double), SeriesMap!(string, double))); 236 static assert (isAssignable!(SeriesMap!(string, double), typeof(null))); 237 238 static assert (isAssignable!(SeriesMap!(const string, double), SeriesMap!(string, double))); 239 static assert (isAssignable!(SeriesMap!(string, const double), SeriesMap!(string, double))); 240 static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, double))); 241 242 static assert (isAssignable!(SeriesMap!(immutable string, double), SeriesMap!(immutable string, double))); 243 static assert (isAssignable!(SeriesMap!(immutable string, const double), SeriesMap!(immutable string, double))); 244 static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(immutable string, double))); 245 static assert (isAssignable!(SeriesMap!(string, immutable double), SeriesMap!(string, immutable double))); 246 static assert (isAssignable!(SeriesMap!(const string, immutable double), SeriesMap!(string, immutable double))); 247 static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, immutable double))); 248 // etc 249 } 250 251 /++ 252 Plain index series data structure. 253 254 `*.index[i]`/`*.key[i]`/`*.time` corresponds to `*.data[i]`/`*.value`. 255 256 Index is assumed to be sorted. 257 $(LREF sort) can be used to normalise a series. 258 +/ 259 @serdeFields 260 struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous) 261 { 262 private enum doUnittest = is(typeof(this) == mir_series!(int*, double*)); 263 264 /// 265 alias IndexIterator = IndexIterator_; 266 267 /// 268 alias Iterator = Iterator_; 269 270 /// 271 @serdeIgnore enum size_t N = N_; 272 273 /// 274 @serdeIgnore enum SliceKind kind = kind_; 275 276 /++ 277 Index series is assumed to be sorted. 278 279 `IndexIterator` is an iterator on top of date, date-time, time, or numbers or user defined types with defined `opCmp`. 280 For example, `Date*`, `DateTime*`, `immutable(long)*`, `mir.ndslice.iterator.IotaIterator`. 281 +/ 282 auto index() @property @trusted 283 { 284 return _index.sliced(this.data._lengths[0]); 285 } 286 287 /// ditto 288 auto index() @property @trusted const 289 { 290 return _index.lightConst.sliced(this.data._lengths[0]); 291 } 292 293 /// ditto 294 auto index() @property @trusted immutable 295 { 296 return _index.lightImmutable.sliced(this.data._lengths[0]); 297 } 298 299 /// ditto 300 void index()(Slice!IndexIterator index) @property @trusted 301 { 302 import core.lifetime: move; 303 this._index = move(index._iterator); 304 } 305 306 /// ditto 307 static if (doUnittest) 308 @safe version(mir_test) unittest 309 { 310 import mir.ndslice.slice: sliced; 311 auto s = ["a", "b"].series([5, 6]); 312 assert(s.index == ["a", "b"]); 313 s.index = ["c", "d"].sliced; 314 assert(s.index == ["c", "d"]); 315 } 316 317 /++ 318 Data is any ndslice with only one constraints, 319 `data` and `index` lengths should be equal. 320 +/ 321 Slice!(Iterator, N, kind) data; 322 323 @serdeIgnore: 324 325 /// 326 IndexIterator _index; 327 328 /// Index / Key / Time type aliases 329 alias Index = typeof(typeof(this).init.index.front); 330 /// Data / Value type aliases 331 alias Data = typeof(typeof(this).init.data.front); 332 333 private enum defaultMsg() = "Series " ~ Unqual!(this.Data).stringof ~ "[" ~ Unqual!(this.Index).stringof ~ "]: Missing"; 334 private static immutable defaultExc() = new Exception(defaultMsg!() ~ " required key"); 335 336 /// 337 void serdeFinalize()() @trusted scope 338 { 339 import mir.algorithm.iteration: any; 340 import mir.ndslice.topology: pairwise; 341 import std.traits: Unqual; 342 if (length <= 1) 343 return; 344 auto mutableOf = cast(Series!(Unqual!Index*, Unqual!Data*)) this.lightScope(); 345 if (any(pairwise!"a > b"(mutableOf.index))) 346 sort(mutableOf); 347 } 348 349 @fmamath: 350 351 /// 352 this()(Slice!IndexIterator index, Slice!(Iterator, N, kind) data) 353 { 354 assert(index.length == data.length, "Series constructor: index and data lengths must be equal."); 355 this.data = data; 356 _index = index._iterator; 357 } 358 359 360 /// Construct from null 361 this(typeof(null)) 362 { 363 this.data = this.data.init; 364 _index = _index.init; 365 } 366 367 /// 368 bool opEquals(RIndexIterator, RIterator, size_t RN, SliceKind rkind, )(Series!(RIndexIterator, RIterator, RN, rkind) rhs) const 369 { 370 return this.lightScopeIndex == rhs.lightScopeIndex && this.data.lightScope == rhs.data.lightScope; 371 } 372 373 private auto lightScopeIndex()() return scope @trusted 374 { 375 return .lightScope(_index).sliced(this.data._lengths[0]); 376 } 377 378 private auto lightScopeIndex()() return scope @trusted const 379 { 380 return .lightScope(_index).sliced(this.data._lengths[0]); 381 } 382 383 private auto lightScopeIndex()() return scope @trusted immutable 384 { 385 return .lightScope(_index).sliced(this.data._lengths[0]); 386 } 387 388 /// 389 typeof(this) opBinary(string op : "~")(typeof(this) rhs) 390 { 391 scope typeof(this.lightScope)[2] lhsAndRhs = [this.lightScope, rhs.lightScope]; 392 return unionSeriesImplPrivate!false(lhsAndRhs); 393 } 394 395 /// ditto 396 auto opBinary(string op : "~")(const typeof(this) rhs) const @trusted 397 { 398 scope typeof(this.lightScope)[2] lhsAndRhs = [this.lightScope, rhs.lightScope]; 399 return unionSeriesImplPrivate!false(lhsAndRhs); 400 } 401 402 static if (doUnittest) 403 /// 404 @safe pure nothrow version(mir_test) unittest 405 { 406 import mir.date: Date; 407 408 ////////////////////////////////////// 409 // Constructs two time-series. 410 ////////////////////////////////////// 411 auto index0 = [1,3,4]; 412 auto data0 = [1.0, 3, 4]; 413 auto series0 = index0.series(data0); 414 415 auto index1 = [1,2,5]; 416 auto data1 = [10.0, 20, 50]; 417 auto series1 = index1.series(data1); 418 419 ////////////////////////////////////// 420 // Merges multiple series into one. 421 ////////////////////////////////////// 422 // Order is matter. 423 // The first slice has higher priority. 424 auto m0 = series0 ~ series1; 425 auto m1 = series1 ~ series0; 426 427 assert(m0.index == m1.index); 428 assert(m0.data == [ 1, 20, 3, 4, 50]); 429 assert(m1.data == [10, 20, 3, 4, 50]); 430 } 431 432 static if (doUnittest) 433 @safe pure nothrow version(mir_test) unittest 434 { 435 import mir.date: Date; 436 437 ////////////////////////////////////// 438 // Constructs two time-series. 439 ////////////////////////////////////// 440 auto index0 = [1,3,4]; 441 auto data0 = [1.0, 3, 4]; 442 auto series0 = index0.series(data0); 443 444 auto index1 = [1,2,5]; 445 auto data1 = [10.0, 20, 50]; 446 const series1 = index1.series(data1); 447 448 ////////////////////////////////////// 449 // Merges multiple series into one. 450 ////////////////////////////////////// 451 // Order is matter. 452 // The first slice has higher priority. 453 auto m0 = series0 ~ series1; 454 auto m1 = series1 ~ series0; 455 456 assert(m0.index == m1.index); 457 assert(m0.data == [ 1, 20, 3, 4, 50]); 458 assert(m1.data == [10, 20, 3, 4, 50]); 459 } 460 461 /++ 462 Special `[] =` index-assign operator for index-series. 463 Assigns data from `r` with index intersection. 464 If a index index in `r` is not in the index index for this series, then no op-assign will take place. 465 This and r series are assumed to be sorted. 466 467 Params: 468 r = rvalue index-series 469 +/ 470 void opIndexAssign(IndexIterator_, Iterator_, size_t N_, SliceKind kind_) 471 (Series!(IndexIterator_, Iterator_, N_, kind_) r) 472 { 473 opIndexOpAssign!("", IndexIterator_, Iterator_, N_, kind_)(r); 474 } 475 476 static if (doUnittest) 477 /// 478 version(mir_test) unittest 479 { 480 auto index = [1, 2, 3, 4]; 481 auto data = [10.0, 10, 10, 10]; 482 auto series = index.series(data); 483 484 auto rindex = [0, 2, 4, 5]; 485 auto rdata = [1.0, 2, 3, 4]; 486 auto rseries = rindex.series(rdata); 487 488 // series[] = rseries; 489 series[] = rseries; 490 assert(series.data == [10, 2, 10, 3]); 491 } 492 493 /++ 494 Special `[] op=` index-op-assign operator for index-series. 495 Op-assigns data from `r` with index intersection. 496 If a index index in `r` is not in the index index for this series, then no op-assign will take place. 497 This and r series are assumed to be sorted. 498 499 Params: 500 rSeries = rvalue index-series 501 +/ 502 void opIndexOpAssign(string op, IndexIterator_, Iterator_, size_t N_, SliceKind kind_) 503 (auto ref Series!(IndexIterator_, Iterator_, N_, kind_) rSeries) 504 { 505 scope l = this.lightScope; 506 scope r = rSeries.lightScope; 507 if (r.empty) 508 return; 509 if (l.empty) 510 return; 511 Unqual!(typeof(*r._index)) rf = *r._index; 512 Unqual!(typeof(*l._index)) lf = *l._index; 513 goto Begin; 514 R: 515 r.popFront; 516 if (r.empty) 517 goto End; 518 rf = *r._index; 519 Begin: 520 if (lf > rf) 521 goto R; 522 if (lf < rf) 523 goto L; 524 E: 525 static if (N != 1) 526 mixin("l.data.front[] " ~ op ~ "= r.data.front;"); 527 else 528 mixin("l.data.front " ~ op ~ "= r.data.front;"); 529 530 r.popFront; 531 if (r.empty) 532 goto End; 533 rf = *r._index; 534 L: 535 l.popFront; 536 if (l.empty) 537 goto End; 538 lf = *l._index; 539 540 if (lf < rf) 541 goto L; 542 if (lf == rf) 543 goto E; 544 goto R; 545 End: 546 } 547 548 static if (doUnittest) 549 /// 550 version(mir_test) unittest 551 { 552 auto index = [1, 2, 3, 4]; 553 auto data = [10.0, 10, 10, 10]; 554 auto series = index.series(data); 555 556 auto rindex = [0, 2, 4, 5]; 557 auto rdata = [1.0, 2, 3, 4]; 558 auto rseries = rindex.series(rdata); 559 560 series[] += rseries; 561 assert(series.data == [10, 12, 10, 13]); 562 } 563 564 /++ 565 This function uses a search with policy sp to find the largest left subrange on which 566 `t < key` is true for all `t`. 567 The search schedule and its complexity are documented in `std.range.SearchPolicy`. 568 +/ 569 auto lowerBound(Index)(auto ref scope const Index key) 570 { 571 return opIndex(opSlice(0, lightScopeIndex.transitionIndex(key))); 572 } 573 574 /// ditto 575 auto lowerBound(Index)(auto ref scope const Index key) const 576 { 577 return opIndex(opSlice(0, lightScopeIndex.transitionIndex(key))); 578 } 579 580 581 /++ 582 This function uses a search with policy sp to find the largest right subrange on which 583 `t > key` is true for all `t`. 584 The search schedule and its complexity are documented in `std.range.SearchPolicy`. 585 +/ 586 auto upperBound(Index)(auto ref scope const Index key) 587 { 588 return opIndex(opSlice(lightScopeIndex.transitionIndex!"a <= b"(key), length)); 589 } 590 591 /// ditto 592 auto upperBound(Index)(auto ref scope const Index key) const 593 { 594 return opIndex(opSlice(lightScopeIndex.transitionIndex!"a <= b"(key), length)); 595 } 596 597 /** 598 Gets data for the index. 599 Params: 600 key = index 601 _default = default value is returned if the series does not contains the index. 602 Returns: 603 data that corresponds to the index or default value. 604 */ 605 ref get(Index, Value)(auto ref scope const Index key, return ref Value _default) @trusted 606 if (!is(Value : const(Exception))) 607 { 608 size_t idx = lightScopeIndex.transitionIndex(key); 609 return idx < this.data._lengths[0] && _index[idx] == key ? this.data[idx] : _default; 610 } 611 612 /// ditto 613 ref get(Index, Value)(auto ref scope const Index key, return ref Value _default) const 614 if (!is(Value : const(Exception))) 615 { 616 return this.lightScope.get(key, _default); 617 } 618 619 /// ditto 620 ref get(Index, Value)(auto ref scope const Index key, return ref Value _default) immutable 621 if (!is(Value : const(Exception))) 622 { 623 return this.lightScope.get(key, _default); 624 } 625 626 auto get(Index, Value)(auto ref scope const Index key, Value _default) @trusted 627 if (!is(Value : const(Exception))) 628 { 629 size_t idx = lightScopeIndex.transitionIndex(key); 630 return idx < this.data._lengths[0] && _index[idx] == key ? this.data[idx] : _default; 631 } 632 633 /// ditto 634 auto get(Index, Value)(auto ref scope const Index key, Value _default) const 635 if (!is(Value : const(Exception))) 636 { 637 import core.lifetime: forward; 638 return this.lightScope.get(key, forward!_default); 639 } 640 641 /// ditto 642 auto get(Index, Value)(auto ref scope const Index key, Value _default) immutable 643 if (!is(Value : const(Exception))) 644 { 645 import core.lifetime: forward; 646 return this.lightScope.get(key, forward!_default); 647 } 648 649 /** 650 Gets data for the index. 651 Params: 652 key = index 653 exc = (lazy, optional) exception to throw if the series does not contains the index. 654 Returns: data that corresponds to the index. 655 Throws: 656 Exception if the series does not contains the index. 657 See_also: $(LREF Series.getVerbose), $(LREF Series.tryGet) 658 */ 659 auto ref get(Index)(auto ref scope const Index key) @trusted 660 { 661 size_t idx = lightScopeIndex.transitionIndex(key); 662 if (idx < this.data._lengths[0] && _index[idx] == key) 663 { 664 return this.data[idx]; 665 } 666 import mir.exception : toMutable; 667 throw defaultExc!().toMutable; 668 } 669 670 /// ditto 671 auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) @trusted 672 { 673 size_t idx = lightScopeIndex.transitionIndex(key); 674 if (idx < this.data._lengths[0] && _index[idx] == key) 675 { 676 return this.data[idx]; 677 } 678 { import mir.exception : toMutable; throw exc.toMutable; } 679 } 680 681 /// ditto 682 auto ref get(Index)(auto ref scope const Index key) const 683 { 684 return this.lightScope.get(key); 685 } 686 687 /// ditto 688 auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) const 689 { 690 return this.lightScope.get(key, exc); 691 } 692 693 694 /// ditto 695 auto ref get(Index)(auto ref scope const Index key) immutable 696 { 697 return this.lightScope.get(key); 698 } 699 700 /// ditto 701 auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) immutable 702 { 703 return this.lightScope.get(key, exc); 704 } 705 706 /** 707 Gets data for the index (verbose exception). 708 Params: 709 key = index 710 Returns: data that corresponds to the index. 711 Throws: 712 Detailed exception if the series does not contains the index. 713 See_also: $(LREF Series.get), $(LREF Series.tryGet) 714 */ 715 auto ref getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__) 716 { 717 import std.format: format; 718 return this.get(key, new Exception(format("%s %s key", defaultMsg!(), key), file, line)); 719 } 720 721 /// ditto 722 auto ref getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__) const 723 { 724 return this.lightScope.getVerbose(key, file, line); 725 } 726 727 /// ditto 728 auto ref getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__) immutable 729 { 730 return this.lightScope.getVerbose(key, file, line); 731 } 732 733 /** 734 Gets data for the index (extra verbose exception). 735 Params: 736 key = index 737 Returns: data that corresponds to the index. 738 Throws: 739 Detailed exception if the series does not contains the index. 740 See_also: $(LREF Series.get), $(LREF Series.tryGet) 741 */ 742 auto ref getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__) 743 { 744 import std.format: format; 745 return this.get(key, new Exception(format("%s. %s %s key", exceptionInto, defaultMsg!(), key), file, line)); 746 } 747 748 /// ditto 749 auto ref getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__) const 750 { 751 return this.lightScope.getExtraVerbose(key, exceptionInto, file, line); 752 } 753 754 /// ditto 755 auto ref getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__) immutable 756 { 757 return this.lightScope.getExtraVerbose(key, exceptionInto, file, line); 758 } 759 760 /// 761 bool contains(Index)(auto ref scope const Index key) const @trusted 762 { 763 size_t idx = lightScopeIndex.transitionIndex(key); 764 return idx < this.data._lengths[0] && _index[idx] == key; 765 } 766 767 /// 768 auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) @trusted 769 { 770 size_t idx = lightScopeIndex.transitionIndex(key); 771 bool cond = idx < this.data._lengths[0] && _index[idx] == key; 772 static if (__traits(compiles, &this.data[size_t.init])) 773 { 774 if (cond) 775 return &this.data[idx]; 776 return null; 777 } 778 else 779 { 780 return bool(cond); 781 } 782 } 783 784 /// ditto 785 auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) const 786 { 787 auto val = key in this.lightScope; 788 return val; 789 } 790 791 /// ditto 792 auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) immutable 793 { 794 auto val = key in this.lightScope; 795 return val; 796 } 797 798 /++ 799 Tries to get the first value, such that `key_i == key`. 800 801 Returns: `true` on success. 802 +/ 803 bool tryGet(Index, Value)(Index key, scope ref Value val) @trusted 804 { 805 size_t idx = lightScopeIndex.transitionIndex(key); 806 auto cond = idx < this.data._lengths[0] && _index[idx] == key; 807 if (cond) 808 val = this.data[idx]; 809 return cond; 810 } 811 812 /// ditto 813 bool tryGet(Index, Value)(Index key, scope ref Value val) const 814 { 815 return this.lightScope.tryGet(key, val); 816 } 817 818 /// ditto 819 bool tryGet(Index, Value)(Index key, scope ref Value val) immutable 820 { 821 return this.lightScope.tryGet(key, val); 822 } 823 824 /++ 825 Tries to get the first value, such that `key_i >= key`. 826 827 Returns: `true` on success. 828 +/ 829 bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) 830 { 831 size_t idx = lightScopeIndex.transitionIndex(key); 832 auto cond = idx < this.data._lengths[0]; 833 if (cond) 834 val = this.data[idx]; 835 return cond; 836 } 837 838 /// ditto 839 bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) const 840 { 841 return this.lightScope.tryGetNext(key, val); 842 } 843 844 /// ditto 845 bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) immutable 846 { 847 return this.lightScope.tryGetNext(key, val); 848 } 849 850 /++ 851 Tries to get the first value, such that `key_i >= key`. 852 Updates `key` with `key_i`. 853 854 Returns: `true` on success. 855 +/ 856 bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) @trusted 857 { 858 size_t idx = lightScopeIndex.transitionIndex(key); 859 auto cond = idx < this.data._lengths[0]; 860 if (cond) 861 { 862 key = _index[idx]; 863 val = this.data[idx]; 864 } 865 return cond; 866 } 867 868 /// ditto 869 bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) const 870 { 871 return this.lightScope.tryGetNextUpdateKey(key, val); 872 } 873 874 /// ditto 875 bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) immutable 876 { 877 return this.lightScope.tryGetNextUpdateKey(key, val); 878 } 879 880 /++ 881 Tries to get the last value, such that `key_i <= key`. 882 883 Returns: `true` on success. 884 +/ 885 bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) 886 { 887 size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; 888 auto cond = 0 <= sizediff_t(idx); 889 if (cond) 890 val = this.data[idx]; 891 return cond; 892 } 893 894 /// ditto 895 bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) const 896 { 897 return this.lightScope.tryGetPrev(key, val); 898 } 899 900 /// ditto 901 bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) immutable 902 { 903 return this.lightScope.tryGetPrev(key, val); 904 } 905 906 /++ 907 Tries to get the last value, such that `key_i <= key`. 908 Updates `key` with `key_i`. 909 910 Returns: `true` on success. 911 +/ 912 bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) @trusted 913 { 914 size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; 915 auto cond = 0 <= sizediff_t(idx); 916 if (cond) 917 { 918 key = _index[idx]; 919 val = this.data[idx]; 920 } 921 return cond; 922 } 923 924 /// ditto 925 bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) const 926 { 927 return this.lightScope.tryGetPrevUpdateKey(key, val); 928 } 929 930 /// ditto 931 bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) immutable 932 { 933 return this.lightScope.tryGetPrevUpdateKey(key, val); 934 } 935 936 /++ 937 Tries to get the first value, such that `lowerBound <= key_i <= upperBound`. 938 939 Returns: `true` on success. 940 +/ 941 bool tryGetFirst(Index, Value)(auto ref scope const Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted 942 { 943 size_t idx = lightScopeIndex.transitionIndex(lowerBound); 944 auto cond = idx < this.data._lengths[0] && _index[idx] <= upperBound; 945 if (cond) 946 val = this.data[idx]; 947 return cond; 948 } 949 950 /// ditto 951 bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const 952 { 953 return this.lightScope.tryGetFirst(lowerBound, upperBound, val); 954 } 955 956 /// ditto 957 bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) immutable 958 { 959 return this.lightScope.tryGetFirst(lowerBound, upperBound, val); 960 } 961 962 /++ 963 Tries to get the first value, such that `lowerBound <= key_i <= upperBound`. 964 Updates `lowerBound` with `key_i`. 965 966 Returns: `true` on success. 967 +/ 968 bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted 969 { 970 size_t idx = lightScopeIndex.transitionIndex(lowerBound); 971 auto cond = idx < this.data._lengths[0] && _index[idx] <= upperBound; 972 if (cond) 973 { 974 lowerBound = _index[idx]; 975 val = this.data[idx]; 976 } 977 return cond; 978 } 979 980 /// ditto 981 bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const 982 { 983 return this.lightScope.tryGetFirstUpdateLower(lowerBound, upperBound, val); 984 } 985 986 /// ditto 987 bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) immutable 988 { 989 return this.lightScope.tryGetFirstUpdateLower(lowerBound, upperBound, val); 990 } 991 992 /++ 993 Tries to get the last value, such that `lowerBound <= key_i <= upperBound`. 994 995 Returns: `true` on success. 996 +/ 997 bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted 998 { 999 size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; 1000 auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; 1001 if (cond) 1002 val = this.data[idx]; 1003 return cond; 1004 } 1005 1006 /// ditto 1007 bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const 1008 { 1009 return this.lightScope.tryGetLast(lowerBound, upperBound, val); 1010 } 1011 1012 /// ditto 1013 bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) immutable 1014 { 1015 return this.lightScope.tryGetLast(lowerBound, upperBound, val); 1016 } 1017 1018 /++ 1019 Tries to get the last value, such that `lowerBound <= key_i <= upperBound`. 1020 Updates `upperBound` with `key_i`. 1021 1022 Returns: `true` on success. 1023 +/ 1024 bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) @trusted 1025 { 1026 size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; 1027 auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; 1028 if (cond) 1029 { 1030 upperBound = _index[idx]; 1031 val = this.data[idx]; 1032 } 1033 return cond; 1034 } 1035 1036 /// ditto 1037 bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) const 1038 { 1039 return this.lightScope.tryGetLastUpdateKey(lowerBound, upperBound, val); 1040 } 1041 1042 /// ditto 1043 bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) immutable 1044 { 1045 return this.lightScope.tryGetLastUpdateKey(lowerBound, upperBound, val); 1046 } 1047 1048 /++ 1049 Returns: 1050 1D Slice with creared with $(NDSLICE topology, zip) ([0] - key, [1] - value). 1051 See_also: 1052 $(NDSLICE topology, map) uses multiargument lambdas to handle zipped slices. 1053 +/ 1054 auto asSlice()() @property 1055 { 1056 import mir.ndslice.topology: zip, map, ipack; 1057 static if (N == 1) 1058 return index.zip(data); 1059 else 1060 return index.zip(data.ipack!1.map!"a"); 1061 } 1062 1063 /// ditto 1064 auto asSlice()() const @property 1065 { 1066 return opIndex.asSlice; 1067 } 1068 1069 /// ditto 1070 auto asSlice()() immutable @property 1071 { 1072 return opIndex.asSlice; 1073 } 1074 1075 /// ndslice-like primitives 1076 bool empty(size_t dimension = 0)() const @property 1077 if (dimension < N) 1078 { 1079 return !length!dimension; 1080 } 1081 1082 /// ditto 1083 size_t length(size_t dimension = 0)() const @property 1084 if (dimension < N) 1085 { 1086 return this.data.length!dimension; 1087 } 1088 1089 /// ditto 1090 auto front(size_t dimension = 0)() @property 1091 if (dimension < N) 1092 { 1093 assert(!empty!dimension); 1094 static if (dimension) 1095 { 1096 return index.series(data.front!dimension); 1097 } 1098 else 1099 { 1100 return Observation!(Index, Data)(index.front, data.front); 1101 } 1102 } 1103 1104 /// ditto 1105 auto back(size_t dimension = 0)() @property 1106 if (dimension < N) 1107 { 1108 assert(!empty!dimension); 1109 static if (dimension) 1110 { 1111 return index.series(this.data.back!dimension); 1112 } 1113 else 1114 { 1115 return index.back.observation(this.data.back); 1116 } 1117 } 1118 1119 /// ditto 1120 void popFront(size_t dimension = 0)() @trusted 1121 if (dimension < N) 1122 { 1123 assert(!empty!dimension); 1124 static if (dimension == 0) 1125 _index++; 1126 this.data.popFront!dimension; 1127 } 1128 1129 /// ditto 1130 void popBack(size_t dimension = 0)() 1131 if (dimension < N) 1132 { 1133 assert(!empty!dimension); 1134 this.data.popBack!dimension; 1135 } 1136 1137 /// ditto 1138 void popFrontExactly(size_t dimension = 0)(size_t n) @trusted 1139 if (dimension < N) 1140 { 1141 assert(length!dimension >= n); 1142 static if (dimension == 0) 1143 _index += n; 1144 this.data.popFrontExactly!dimension(n); 1145 } 1146 1147 /// ditto 1148 void popBackExactly(size_t dimension = 0)(size_t n) 1149 if (dimension < N) 1150 { 1151 assert(length!dimension >= n); 1152 this.data.popBackExactly!dimension(n); 1153 } 1154 1155 /// ditto 1156 void popFrontN(size_t dimension = 0)(size_t n) 1157 if (dimension < N) 1158 { 1159 auto len = length!dimension; 1160 n = n <= len ? n : len; 1161 popFrontExactly!dimension(n); 1162 } 1163 1164 /// ditto 1165 void popBackN(size_t dimension = 0)(size_t n) 1166 if (dimension < N) 1167 { 1168 auto len = length!dimension; 1169 n = n <= len ? n : len; 1170 popBackExactly!dimension(n); 1171 } 1172 1173 /// ditto 1174 Slice!(IotaIterator!size_t) opSlice(size_t dimension = 0)(size_t i, size_t j) const 1175 if (dimension < N) 1176 in 1177 { 1178 assert(i <= j, 1179 "Series.opSlice!" ~ dimension.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); 1180 enum errorMsg = ": difference between the right and the left bounds" 1181 ~ " must be less than or equal to the length of the given dimension."; 1182 assert(j - i <= this.data._lengths[dimension], 1183 "Series.opSlice!" ~ dimension.stringof ~ errorMsg); 1184 } 1185 do 1186 { 1187 return typeof(return)(j - i, typeof(return).Iterator(i)); 1188 } 1189 1190 /// ditto 1191 size_t opDollar(size_t dimension = 0)() const 1192 { 1193 return this.data.opDollar!dimension; 1194 } 1195 1196 /// ditto 1197 auto opIndex(Slices...)(Slices slices) 1198 if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) 1199 { 1200 static if (Slices.length == 0) 1201 { 1202 return this; 1203 } 1204 else 1205 static if (is_Slice!(Slices[0])) 1206 { 1207 return index[slices[0]].series(data[slices]); 1208 } 1209 else 1210 { 1211 return index[slices[0]].observation(data[slices]); 1212 } 1213 } 1214 1215 /// ditto 1216 auto opIndex(Slices...)(Slices slices) const 1217 if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) 1218 { 1219 return lightConst.opIndex(slices); 1220 } 1221 1222 /// ditto 1223 auto opIndex(Slices...)(Slices slices) immutable 1224 if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) 1225 { 1226 return lightImmutable.opIndex(slices); 1227 } 1228 1229 /// 1230 ref opAssign(typeof(this) rvalue) scope return @trusted 1231 { 1232 import mir.utility: swap; 1233 this.data._structure = rvalue.data._structure; 1234 swap(this.data._iterator, rvalue.data._iterator); 1235 swap(this._index, rvalue._index); 1236 return this; 1237 } 1238 1239 /// ditto 1240 ref opAssign(RIndexIterator, RIterator)(Series!(RIndexIterator, RIterator, N, kind) rvalue) scope return 1241 if (isAssignable!(IndexIterator, RIndexIterator) && isAssignable!(Iterator, RIterator)) 1242 { 1243 import core.lifetime: move; 1244 this.data._structure = rvalue.data._structure; 1245 this.data._iterator = rvalue.data._iterator.move; 1246 this._index = rvalue._index.move; 1247 return this; 1248 } 1249 1250 /// ditto 1251 ref opAssign(RIndexIterator, RIterator)(auto ref const Series!(RIndexIterator, RIterator, N, kind) rvalue) scope return 1252 if (isAssignable!(IndexIterator, LightConstOf!RIndexIterator) && isAssignable!(Iterator, LightConstOf!RIterator)) 1253 { 1254 return this = rvalue.opIndex; 1255 } 1256 1257 /// ditto 1258 ref opAssign(RIndexIterator, RIterator)(auto ref immutable Series!(RIndexIterator, RIterator, N, kind) rvalue) scope return 1259 if (isAssignable!(IndexIterator, LightImmutableOf!RIndexIterator) && isAssignable!(Iterator, LightImmutableOf!RIterator)) 1260 { 1261 return this = rvalue.opIndex; 1262 } 1263 1264 /// ditto 1265 ref opAssign(typeof(null)) scope return 1266 { 1267 return this = this.init; 1268 } 1269 1270 /// ditto 1271 auto save()() @property 1272 { 1273 return this; 1274 } 1275 1276 /// 1277 Series!(LightScopeOf!IndexIterator, LightScopeOf!Iterator, N, kind) lightScope()() return scope @trusted @property 1278 { 1279 return typeof(return)(lightScopeIndex, this.data.lightScope); 1280 } 1281 1282 /// ditto 1283 Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return scope @trusted const @property 1284 { 1285 return typeof(return)(lightScopeIndex, this.data.lightScope); 1286 } 1287 1288 /// ditto 1289 Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return scope @trusted immutable @property 1290 { 1291 return typeof(return)(lightScopeIndex, this.data.lightScope); 1292 } 1293 1294 /// 1295 Series!(LightConstOf!IndexIterator, LightConstOf!Iterator, N, kind) lightConst()() const @property @trusted 1296 { 1297 return index[].series(data[]); 1298 } 1299 1300 /// 1301 Series!(LightImmutableOf!IndexIterator, LightImmutableOf!Iterator, N, kind) lightImmutable()() immutable @property @trusted 1302 { 1303 return index[].series(data[]); 1304 } 1305 1306 /// 1307 auto toConst()() const @property 1308 { 1309 return index.toConst.series(data.toConst); 1310 } 1311 1312 /// 1313 void toString(Writer)(scope ref Writer w) scope const @safe 1314 { 1315 import mir.format: print; 1316 scope ls = lightScope; 1317 print(w, "{ index: "); 1318 print(w, ls.index); 1319 print(w, ", data: "); 1320 print(w, ls.data); 1321 print(w, " }"); 1322 } 1323 1324 } 1325 1326 1327 /// ditto 1328 alias Series = mir_series; 1329 1330 /// 1-dimensional data 1331 @safe pure version(mir_test) unittest 1332 { 1333 auto index = [1, 2, 3, 4]; 1334 auto data = [2.1, 3.4, 5.6, 7.8]; 1335 auto series = index.series(data); 1336 const cseries = series; 1337 1338 assert(series.contains(2)); 1339 assert( ()@trusted{ return (2 in series) is &data[1]; }() ); 1340 1341 assert(!series.contains(5)); 1342 assert( ()@trusted{ return (5 in series) is null; }() ); 1343 1344 assert(series.lowerBound(2) == series[0 .. 1]); 1345 assert(series.upperBound(2) == series[2 .. $]); 1346 1347 assert(cseries.lowerBound(2) == cseries[0 .. 1]); 1348 assert(cseries.upperBound(2) == cseries[2 .. $]); 1349 1350 // slicing type deduction for const / immutable series 1351 static assert(is(typeof(series[]) == 1352 Series!(int*, double*))); 1353 static assert(is(typeof(cseries[]) == 1354 Series!(const(int)*, const(double)*))); 1355 static assert(is(typeof((cast(immutable) series)[]) == 1356 Series!(immutable(int)*, immutable(double)*))); 1357 1358 /// slicing 1359 auto seriesSlice = series[1 .. $ - 1]; 1360 assert(seriesSlice.index == index[1 .. $ - 1]); 1361 assert(seriesSlice.data == data[1 .. $ - 1]); 1362 static assert(is(typeof(series) == typeof(seriesSlice))); 1363 1364 /// indexing 1365 assert(series[1] == observation(2, 3.4)); 1366 1367 /// range primitives 1368 assert(series.length == 4); 1369 assert(series.front == observation(1, 2.1)); 1370 1371 series.popFront; 1372 assert(series.front == observation(2, 3.4)); 1373 1374 series.popBackN(10); 1375 assert(series.empty); 1376 } 1377 1378 /// 2-dimensional data 1379 @safe pure version(mir_test) unittest 1380 { 1381 import mir.date: Date; 1382 import mir.ndslice.topology: canonical, iota; 1383 1384 size_t row_length = 5; 1385 1386 auto index = [ 1387 Date(2017, 01, 01), 1388 Date(2017, 02, 01), 1389 Date(2017, 03, 01), 1390 Date(2017, 04, 01)]; 1391 1392 // 1, 2, 3, 4, 5 1393 // 6, 7, 8, 9, 10 1394 // 11, 12, 13, 14, 15 1395 // 16, 17, 18, 19, 20 1396 auto data = iota!int([index.length, row_length], 1); 1397 1398 // canonical and universal ndslices are more flexible then contiguous 1399 auto series = index.series(data.canonical); 1400 1401 /// slicing 1402 auto seriesSlice = series[1 .. $ - 1, 2 .. 4]; 1403 assert(seriesSlice.index == index[1 .. $ - 1]); 1404 assert(seriesSlice.data == data[1 .. $ - 1, 2 .. 4]); 1405 1406 static if (kindOf!(typeof(series.data)) != Contiguous) 1407 static assert(is(typeof(series) == typeof(seriesSlice))); 1408 1409 /// indexing 1410 assert(series[1, 4] == observation(Date(2017, 02, 01), 10)); 1411 assert(series[2] == observation(Date(2017, 03, 01), iota!int([row_length], 11))); 1412 1413 /// range primitives 1414 assert(series.length == 4); 1415 assert(series.length!1 == 5); 1416 1417 series.popFront!1; 1418 assert(series.length!1 == 4); 1419 } 1420 1421 /// Construct from null 1422 @safe pure nothrow @nogc version(mir_test) unittest 1423 { 1424 import mir.series; 1425 alias Map = Series!(string*, double*); 1426 Map a = null; 1427 auto b = Map(null); 1428 assert(a.empty); 1429 assert(b.empty); 1430 1431 auto fun(Map a = null) 1432 { 1433 1434 } 1435 } 1436 1437 version(mir_test) 1438 /// 1439 @safe unittest 1440 { 1441 import mir.series: series, sort; 1442 auto s = ["b", "a"].series([9, 8]).sort; 1443 1444 import mir.format : text; 1445 assert(s.text == `{ index: [a, b], data: [8, 9] }`); 1446 } 1447 1448 /++ 1449 Convenient function for $(LREF Series) construction. 1450 See_also: $(LREF assocArray) 1451 Attention: 1452 This overloads do not sort the data. 1453 User should call $(LREF directly) if index was not sorted. 1454 +/ 1455 auto series(IndexIterator, Iterator, size_t N, SliceKind kind) 1456 ( 1457 Slice!IndexIterator index, 1458 Slice!(Iterator, N, kind) data, 1459 ) 1460 { 1461 assert(index.length == data.length); 1462 return Series!(IndexIterator, Iterator, N, kind)(index, data); 1463 } 1464 1465 /// ditto 1466 auto series(Index, Data)(Index[] index, Data[] data) 1467 { 1468 assert(index.length == data.length); 1469 return .series(index.sliced, data.sliced); 1470 } 1471 1472 /// ditto 1473 auto series(IndexIterator, Data)(Slice!IndexIterator index, Data[] data) 1474 { 1475 assert(index.length == data.length); 1476 return .series(index, data.sliced); 1477 } 1478 1479 /// ditto 1480 auto series(Index, Iterator, size_t N, SliceKind kind)(Index[] index, Slice!(Iterator, N, kind) data) 1481 { 1482 assert(index.length == data.length); 1483 return .series(index.sliced, data); 1484 } 1485 1486 /** 1487 Constructs a GC-allocated series from an associative array. 1488 Performs exactly two allocations. 1489 1490 Params: 1491 aa = associative array or a pointer to associative array 1492 Returns: 1493 sorted GC-allocated series. 1494 See_also: $(LREF assocArray) 1495 */ 1496 Series!(K*, V*) series(RK, RV, K = RK, V = RV)(RV[RK] aa) 1497 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1498 { 1499 import mir.conv: to; 1500 const size_t length = aa.length; 1501 alias R = typeof(return); 1502 if (__ctfe) 1503 { 1504 K[] keys; 1505 V[] values; 1506 foreach(ref kv; aa.byKeyValue) 1507 { 1508 keys ~= kv.key.to!K; 1509 values ~= kv.value.to!V; 1510 } 1511 auto ret = series(keys, values); 1512 .sort((()@trusted=>cast(Series!(Unqual!K*, Unqual!V*))ret)()); 1513 static if (is(typeof(ret) == typeof(return))) 1514 return ret; 1515 else 1516 return ()@trusted{ return *cast(R*) &ret; }(); 1517 } 1518 import mir.ndslice.allocation: uninitSlice; 1519 Series!(Unqual!K*, Unqual!V*) ret = series(length.uninitSlice!(Unqual!K), length.uninitSlice!(Unqual!V)); 1520 auto it = ret; 1521 foreach(ref kv; aa.byKeyValue) 1522 { 1523 import mir.conv: emplaceRef; 1524 emplaceRef!K(it.index.front, kv.key.to!K); 1525 emplaceRef!V(it.data.front, kv.value.to!V); 1526 it.popFront; 1527 } 1528 .sort(ret); 1529 static if (is(typeof(ret) == typeof(return))) 1530 return ret; 1531 else 1532 return ()@trusted{ return *cast(R*) &ret; }(); 1533 } 1534 1535 /// ditto 1536 Series!(RK*, RV*) series(K, V, RK = const K, RV = const V)(const V[K] aa) 1537 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1538 { 1539 return .series!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); 1540 } 1541 1542 /// ditto 1543 Series!(RK*, RV*) series( K, V, RK = immutable K, RV = immutable V)(immutable V[K] aa) 1544 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1545 { 1546 return .series!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); 1547 } 1548 1549 /// ditto 1550 auto series(K, V)(V[K]* aa) 1551 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1552 { 1553 return series(*a); 1554 } 1555 1556 /// 1557 @safe pure nothrow version(mir_test) unittest 1558 { 1559 auto s = [1: 1.5, 3: 3.3, 2: 20.9].series; 1560 assert(s.index == [1, 2, 3]); 1561 assert(s.data == [1.5, 20.9, 3.3]); 1562 assert(s.data[s.findIndex(2)] == 20.9); 1563 } 1564 1565 pure nothrow version(mir_test) unittest 1566 { 1567 immutable aa = [1: 1.5, 3: 3.3, 2: 2.9]; 1568 auto s = aa.series; 1569 s = cast() s; 1570 s = cast(const) s; 1571 s = cast(immutable) s; 1572 s = s; 1573 assert(s.index == [1, 2, 3]); 1574 assert(s.data == [1.5, 2.9, 3.3]); 1575 assert(s.data[s.findIndex(2)] == 2.9); 1576 } 1577 1578 1579 /** 1580 Constructs a RC-allocated series from an associative array. 1581 Performs exactly two allocations. 1582 1583 Params: 1584 aa = associative array or a pointer to associative array 1585 Returns: 1586 sorted RC-allocated series. 1587 See_also: $(LREF assocArray) 1588 */ 1589 auto rcseries(RK, RV, K = RK, V = RV)(RV[RK] aa) 1590 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1591 { 1592 import mir.rc.array; 1593 import mir.conv: to; 1594 alias R = Series!(RCI!K, RCI!V); 1595 const size_t length = aa.length; 1596 auto ret = series(length.mininitRcarray!(Unqual!K).asSlice, length.mininitRcarray!(Unqual!V).asSlice); 1597 auto it = ret.lightScope; 1598 foreach(ref kv; aa.byKeyValue) 1599 { 1600 import mir.conv: emplaceRef; 1601 emplaceRef!K(it.lightScopeIndex.front, kv.key.to!K); 1602 emplaceRef!V(it.data.front, kv.value.to!V); 1603 it.popFront; 1604 } 1605 import core.lifetime: move; 1606 .sort(ret.lightScope); 1607 static if (is(typeof(ret) == R)) 1608 return ret; 1609 else 1610 return ()@trusted{ return (*cast(R*) &ret); }(); 1611 } 1612 1613 /// ditto 1614 auto rcseries(K, V, RK = const K, RV = const V)(const V[K] aa) 1615 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1616 { 1617 return .rcseries!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); 1618 } 1619 1620 /// ditto 1621 auto rcseries( K, V, RK = immutable K, RV = immutable V)(immutable V[K] aa) 1622 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1623 { 1624 return .rcseries!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); 1625 } 1626 1627 /// ditto 1628 auto rcseries(K, V)(V[K]* aa) 1629 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1630 { 1631 return rcseries(*a); 1632 } 1633 1634 /// 1635 @safe pure nothrow version(mir_test) unittest 1636 { 1637 auto s = [1: 1.5, 3: 3.3, 2: 20.9].rcseries; 1638 assert(s.index == [1, 2, 3]); 1639 assert(s.data == [1.5, 20.9, 3.3]); 1640 assert(s.data[s.findIndex(2)] == 20.9); 1641 } 1642 1643 // pure nothrow 1644 version(mir_test) unittest 1645 { 1646 import mir.rc.array; 1647 immutable aa = [1: 1.5, 3: 3.3, 2: 2.9]; 1648 auto s = aa.rcseries; 1649 Series!(RCI!(const int), RCI!(const double)) c; 1650 s = cast() s; 1651 c = s; 1652 s = cast(const) s; 1653 s = cast(immutable) s; 1654 s = s; 1655 assert(s.index == [1, 2, 3]); 1656 assert(s.data == [1.5, 2.9, 3.3]); 1657 assert(s.data[s.findIndex(2)] == 2.9); 1658 } 1659 1660 /++ 1661 Constructs a manually allocated series from an associative array. 1662 Performs exactly two allocations. 1663 1664 Params: 1665 aa == associative array or a pointer to associative array 1666 Returns: 1667 sorted manually allocated series. 1668 +/ 1669 Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K] aa) 1670 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1671 { 1672 import mir.ndslice.allocation: makeUninitSlice; 1673 import mir.conv: emplaceRef; 1674 1675 immutable size_t length = aa.length; 1676 1677 auto ret = series( 1678 allocator.makeUninitSlice!(Unqual!K)(length), 1679 allocator.makeUninitSlice!(Unqual!V)(length)); 1680 1681 auto it = ret; 1682 foreach(ref kv; aa.byKeyValue) 1683 { 1684 it.index.front.emplaceRef!K(kv.key); 1685 it.data.front.emplaceRef!V(kv.value); 1686 it.popFront; 1687 } 1688 1689 ret.sort; 1690 static if (is(typeof(ret) == typeof(return))) 1691 return ret; 1692 else 1693 return ()@trusted{ return cast(typeof(return)) ret; }(); 1694 } 1695 1696 /// ditto 1697 Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K]* aa) 1698 if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) 1699 { 1700 return makeSeries(allocator, *a); 1701 } 1702 1703 /// 1704 pure nothrow version(mir_test) unittest 1705 { 1706 import std.experimental.allocator; 1707 import std.experimental.allocator.building_blocks.region; 1708 1709 InSituRegion!(1024) allocator; 1710 auto aa = [1: 1.5, 3: 3.3, 2: 2.9]; 1711 1712 auto s = (double[int] aa) @nogc @trusted pure nothrow { 1713 return allocator.makeSeries(aa); 1714 }(aa); 1715 1716 auto indexArray = s.index.field; 1717 auto dataArray = s.data.field; 1718 1719 assert(s.index == [1, 2, 3]); 1720 assert(s.data == [1.5, 2.9, 3.3]); 1721 assert(s.data[s.findIndex(2)] == 2.9); 1722 1723 allocator.dispose(indexArray); 1724 allocator.dispose(dataArray); 1725 } 1726 1727 /++ 1728 Returns a newly allocated associative array from a range of key/value tuples. 1729 1730 Params: 1731 series = index / time $(LREF Series), may not be sorted 1732 1733 Returns: A newly allocated associative array out of elements of the input 1734 _series. Returns a null associative 1735 array reference when given an empty _series. 1736 1737 Duplicates: Associative arrays have unique keys. If r contains duplicate keys, 1738 then the result will contain the value of the last pair for that key in r. 1739 +/ 1740 auto assocArray(IndexIterator, Iterator, size_t N, SliceKind kind) 1741 (Series!(IndexIterator, Iterator, N, kind) series) 1742 { 1743 alias SK = series.Index; 1744 alias SV = series.Data; 1745 alias UK = Unqual!SK; 1746 alias UV = Unqual!SV; 1747 static if (isImplicitlyConvertible!(SK, UK)) 1748 alias K = UK; 1749 else 1750 alias K = SK; 1751 static if (isImplicitlyConvertible!(SV, UV)) 1752 alias V = UV; 1753 else 1754 alias V = SV; 1755 static assert(isMutable!V, "mir.series.assocArray: value type ( " ~ V.stringof ~ " ) must be mutable"); 1756 1757 V[K] aa; 1758 aa.insertOrAssign = series; 1759 return aa; 1760 } 1761 1762 /// 1763 @safe pure version(mir_test) unittest 1764 { 1765 import mir.ndslice; //iota and etc 1766 import mir.series; 1767 1768 auto s = ["c", "a", "b"].series(3.iota!int); 1769 assert(s.assocArray == [ 1770 "c": 0, 1771 "a": 1, 1772 "b": 2, 1773 ]); 1774 } 1775 1776 /// Returns: true if `U` is a $(LREF Series); 1777 enum isSeries(U) = is(U : Series!(IndexIterator, Iterator, N, kind), IndexIterator, Iterator, size_t N, SliceKind kind); 1778 1779 /++ 1780 Finds an index such that `series.index[index] == key`. 1781 1782 Params: 1783 series = series 1784 key = index to find in the series 1785 Returns: 1786 `size_t.max` if the series does not contain the key and appropriate index otherwise. 1787 +/ 1788 size_t findIndex(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key) 1789 { 1790 auto idx = series.lightScopeIndex.transitionIndex(key); 1791 if (idx < series.data._lengths[0] && series.index[idx] == key) 1792 { 1793 return idx; 1794 } 1795 return size_t.max; 1796 } 1797 1798 /// 1799 @safe pure nothrow version(mir_test) unittest 1800 { 1801 auto index = [1, 2, 3, 4].sliced; 1802 auto data = [2.1, 3.4, 5.6, 7.8].sliced; 1803 auto series = index.series(data); 1804 1805 assert(series.data[series.findIndex(3)] == 5.6); 1806 assert(series.findIndex(0) == size_t.max); 1807 } 1808 1809 /++ 1810 Finds a backward index such that `series.index[$ - backward_index] == key`. 1811 1812 Params: 1813 series = series 1814 key = index key to find in the series 1815 Returns: 1816 `0` if the series does not contain the key and appropriate backward index otherwise. 1817 +/ 1818 size_t find(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key) 1819 { 1820 auto idx = series.lightScopeIndex.transitionIndex(key); 1821 auto bidx = series.data._lengths[0] - idx; 1822 if (bidx && series.index[idx] == key) 1823 { 1824 return bidx; 1825 } 1826 return 0; 1827 } 1828 1829 /// 1830 @safe pure nothrow version(mir_test) unittest 1831 { 1832 auto index = [1, 2, 3, 4].sliced; 1833 auto data = [2.1, 3.4, 5.6, 7.8].sliced; 1834 auto series = index.series(data); 1835 1836 if (auto bi = series.find(3)) 1837 { 1838 assert(series.data[$ - bi] == 5.6); 1839 } 1840 else 1841 { 1842 assert(0); 1843 } 1844 1845 assert(series.find(0) == 0); 1846 } 1847 1848 /++ 1849 Iterates union using three functions to handle each intersection case separately. 1850 Params: 1851 lfun = binary function that accepts left side key (and left side value) 1852 cfun = trinary function that accepts left side key, (left side value,) and right side value 1853 rfun = binary function that accepts right side key (and right side value) 1854 +/ 1855 template troykaGalop(alias lfun, alias cfun, alias rfun) 1856 { 1857 import mir.primitives: isInputRange; 1858 1859 /++ 1860 Params: 1861 lhs = left hand series 1862 rhs = right hand series 1863 +/ 1864 pragma(inline, false) 1865 void troykaGalop( 1866 IndexIterL, IterL, size_t LN, SliceKind lkind, 1867 IndexIterR, IterR, size_t RN, SliceKind rkind, 1868 )( 1869 Series!(IndexIterL, IterL, LN, lkind) lhs, 1870 Series!(IndexIterR, IterR, RN, rkind) rhs, 1871 ) 1872 { 1873 if (lhs.empty) 1874 goto R0; 1875 if (rhs.empty) 1876 goto L1; 1877 for(;;) 1878 { 1879 if (lhs.index.front < rhs.index.front) 1880 { 1881 lfun(lhs.index.front, lhs.data.front); 1882 lhs.popFront; 1883 if (lhs.empty) 1884 goto R1; 1885 continue; 1886 } 1887 else 1888 if (lhs.index.front > rhs.index.front) 1889 { 1890 rfun(rhs.index.front, rhs.data.front); 1891 rhs.popFront; 1892 if (rhs.empty) 1893 goto L1; 1894 continue; 1895 } 1896 else 1897 { 1898 cfun(lhs.index.front, lhs.data.front, rhs.data.front); 1899 lhs.popFront; 1900 rhs.popFront; 1901 if (rhs.empty) 1902 goto L0; 1903 if (lhs.empty) 1904 goto R1; 1905 continue; 1906 } 1907 } 1908 1909 L0: 1910 if (lhs.empty) 1911 return; 1912 L1: 1913 do 1914 { 1915 lfun(lhs.index.front, lhs.data.front); 1916 lhs.popFront; 1917 } while(!lhs.empty); 1918 return; 1919 1920 R0: 1921 if (rhs.empty) 1922 return; 1923 R1: 1924 do 1925 { 1926 rfun(rhs.index.front, rhs.data.front); 1927 rhs.popFront; 1928 } while(!rhs.empty); 1929 return; 1930 } 1931 1932 /++ 1933 Params: 1934 lhs = left hand input range 1935 rhs = right hand input range 1936 +/ 1937 pragma(inline, false) 1938 void troykaGalop (LeftRange, RightRange)(LeftRange lhs, RightRange rhs) 1939 if (isInputRange!LeftRange && isInputRange!RightRange && !isSeries!LeftRange && !isSeries!RightRange) 1940 { 1941 if (lhs.empty) 1942 goto R0; 1943 if (rhs.empty) 1944 goto L1; 1945 for(;;) 1946 { 1947 if (lhs.front < rhs.front) 1948 { 1949 lfun(lhs.front); 1950 lhs.popFront; 1951 if (lhs.empty) 1952 goto R1; 1953 continue; 1954 } 1955 else 1956 if (lhs.front > rhs.front) 1957 { 1958 rfun(rhs.front); 1959 rhs.popFront; 1960 if (rhs.empty) 1961 goto L1; 1962 continue; 1963 } 1964 else 1965 { 1966 cfun(lhs.front, rhs.front); 1967 lhs.popFront; 1968 rhs.popFront; 1969 if (rhs.empty) 1970 goto L0; 1971 if (lhs.empty) 1972 goto R1; 1973 continue; 1974 } 1975 } 1976 1977 L0: 1978 if (lhs.empty) 1979 return; 1980 L1: 1981 do 1982 { 1983 lfun(lhs.front); 1984 lhs.popFront; 1985 } while(!lhs.empty); 1986 return; 1987 1988 R0: 1989 if (rhs.empty) 1990 return; 1991 R1: 1992 do 1993 { 1994 rfun(rhs.front); 1995 rhs.popFront; 1996 } while(!rhs.empty); 1997 return; 1998 } 1999 } 2000 2001 /++ 2002 Constructs union using three functions to handle each intersection case separately. 2003 Params: 2004 lfun = binary function that accepts left side key and left side value 2005 cfun = trinary function that accepts left side key, left side value, and right side value 2006 rfun = binary function that accepts right side key and right side value 2007 +/ 2008 template troykaSeries(alias lfun, alias cfun, alias rfun) 2009 { 2010 /++ 2011 Params: 2012 lhs = left hand series 2013 rhs = right hand series 2014 Returns: 2015 GC-allocated union series with length equal to $(LREF troykaLength) 2016 +/ 2017 auto troykaSeries 2018 ( 2019 IndexIterL, IterL, size_t LN, SliceKind lkind, 2020 IndexIterR, IterR, size_t RN, SliceKind rkind, 2021 )( 2022 Series!(IndexIterL, IterL, LN, lkind) lhs, 2023 Series!(IndexIterR, IterR, RN, rkind) rhs, 2024 ) 2025 { 2026 alias I = CommonType!(typeof(lhs.index.front), typeof(rhs.index.front)); 2027 alias E = CommonType!( 2028 typeof(lfun(lhs.index.front, lhs.data.front)), 2029 typeof(cfun(lhs.index.front, lhs.data.front, rhs.data.front)), 2030 typeof(rfun(rhs.index.front, rhs.data.front)), 2031 ); 2032 alias R = Series!(I*, E*); 2033 alias UI = Unqual!I; 2034 alias UE = Unqual!E; 2035 const length = troykaLength(lhs.index, rhs.index); 2036 import mir.ndslice.allocation: uninitSlice; 2037 auto index = length.uninitSlice!UI; 2038 auto data = length.uninitSlice!UE; 2039 auto ret = index.series(data); 2040 alias algo = troykaSeriesImpl!(lfun, cfun, rfun); 2041 algo!(I, E)(lhs.lightScope, rhs.lightScope, ret); 2042 return (()@trusted => cast(R) ret)(); 2043 } 2044 } 2045 2046 /// 2047 version(mir_test) unittest 2048 { 2049 import mir.ndslice; 2050 auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1)); 2051 auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0); 2052 alias unionAlgorithm = troykaSeries!( 2053 (key, left) => left, 2054 (key, left, right) => left + right, 2055 (key, right) => -right, 2056 ); 2057 auto c = unionAlgorithm(a, b); 2058 assert(c.index == [0, 1, 2, 3, 4, 9]); 2059 assert(c.data == [-10, 1, 22, 3, -30, 44]); 2060 } 2061 2062 /++ 2063 Constructs union using three functions to handle each intersection case separately. 2064 Params: 2065 lfun = binary function that accepts left side key and left side value 2066 cfun = trinary function that accepts left side key, left side value, and right side value 2067 rfun = binary function that accepts right side key and right side value 2068 +/ 2069 template rcTroykaSeries(alias lfun, alias cfun, alias rfun) 2070 { 2071 /++ 2072 Params: 2073 lhs = left hand series 2074 rhs = right hand series 2075 Returns: 2076 RC-allocated union series with length equal to $(LREF troykaLength) 2077 +/ 2078 auto rcTroykaSeries 2079 ( 2080 IndexIterL, IterL, size_t LN, SliceKind lkind, 2081 IndexIterR, IterR, size_t RN, SliceKind rkind, 2082 )( 2083 auto ref Series!(IndexIterL, IterL, LN, lkind) lhs, 2084 auto ref Series!(IndexIterR, IterR, RN, rkind) rhs, 2085 ) 2086 { 2087 import mir.rc.array; 2088 alias I = CommonType!(typeof(lhs.index.front), typeof(rhs.index.front)); 2089 alias E = CommonType!( 2090 typeof(lfun(lhs.index.front, lhs.data.front)), 2091 typeof(cfun(lhs.index.front, lhs.data.front, rhs.data.front)), 2092 typeof(rfun(rhs.index.front, rhs.data.front)), 2093 ); 2094 alias R = Series!(RCI!I, RCI!E); 2095 alias UI = Unqual!I; 2096 alias UE = Unqual!E; 2097 const length = troykaLength(lhs.index, rhs.index); 2098 import mir.ndslice.allocation: uninitSlice; 2099 auto ret = length.mininitRcarray!UI.asSlice.series(length.mininitRcarray!UE.asSlice); 2100 alias algo = troykaSeriesImpl!(lfun, cfun, rfun); 2101 algo!(I, E)(lhs.lightScope, rhs.lightScope, ret.lightScope); 2102 return (()@trusted => *cast(R*) &ret)(); 2103 } 2104 } 2105 2106 /// 2107 version(mir_test) unittest 2108 { 2109 import mir.ndslice; 2110 auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1)); 2111 auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0); 2112 alias unionAlgorithm = rcTroykaSeries!( 2113 (key, left) => left, 2114 (key, left, right) => left + right, 2115 (key, right) => -right, 2116 ); 2117 auto c = unionAlgorithm(a, b); 2118 assert(c.index == [0, 1, 2, 3, 4, 9]); 2119 assert(c.data == [-10, 1, 22, 3, -30, 44]); 2120 } 2121 2122 2123 /++ 2124 Length for Troyka union handlers. 2125 Params: 2126 lhs = left hand side series/range 2127 rhs = right hand side series/range 2128 Returns: Total count of lambda function calls in $(LREF troykaGalop) union handler. 2129 +/ 2130 size_t troykaLength( 2131 IndexIterL, IterL, size_t LN, SliceKind lkind, 2132 IndexIterR, IterR, size_t RN, SliceKind rkind, 2133 )( 2134 Series!(IndexIterL, IterL, LN, lkind) lhs, 2135 Series!(IndexIterR, IterR, RN, rkind) rhs, 2136 ) 2137 { 2138 return troykaLength(lhs.index, rhs.index); 2139 } 2140 2141 /// ditto 2142 size_t troykaLength(LeftRange, RightRange)(LeftRange lhs, RightRange rhs) 2143 if (!isSeries!LeftRange && !isSeries!RightRange) 2144 { 2145 size_t length; 2146 alias counter = (scope auto ref _) => ++length; 2147 alias ccounter = (scope auto ref _l, scope auto ref _r) => ++length; 2148 troykaGalop!(counter, ccounter, counter)(lhs, rhs); 2149 return length; 2150 } 2151 2152 /// 2153 template troykaSeriesImpl(alias lfun, alias cfun, alias rfun) 2154 { 2155 /// 2156 void troykaSeriesImpl 2157 ( 2158 I, E, 2159 IndexIterL, IterL, size_t LN, SliceKind lkind, 2160 IndexIterR, IterR, size_t RN, SliceKind rkind, 2161 UI, UE, 2162 )( 2163 Series!(IndexIterL, IterL, LN, lkind) lhs, 2164 Series!(IndexIterR, IterR, RN, rkind) rhs, 2165 Series!(UI*, UE*) uninitSlice, 2166 ) 2167 { 2168 import mir.conv: emplaceRef; 2169 troykaGalop!( 2170 (auto ref key, auto ref value) { 2171 uninitSlice.index.front.emplaceRef!I(key); 2172 uninitSlice.data.front.emplaceRef!E(lfun(key, value)); 2173 uninitSlice.popFront; 2174 }, 2175 (auto ref key, auto ref lvalue, auto ref rvalue) { 2176 uninitSlice.index.front.emplaceRef!I(key); 2177 uninitSlice.data.front.emplaceRef!E(cfun(key, lvalue, rvalue)); 2178 uninitSlice.popFront; 2179 }, 2180 (auto ref key, auto ref value) { 2181 uninitSlice.index.front.emplaceRef!I(key); 2182 uninitSlice.data.front.emplaceRef!E(rfun(key, value)); 2183 uninitSlice.popFront; 2184 }, 2185 )(lhs, rhs); 2186 assert(uninitSlice.length == 0); 2187 } 2188 } 2189 2190 /** 2191 Merges multiple (time) series into one. 2192 Makes exactly one memory allocation for two series union 2193 and two memory allocation for three and more series union. 2194 2195 Returns: sorted GC-allocated series. 2196 See_also $(LREF Series.opBinary) $(LREF makeUnionSeries) 2197 */ 2198 auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( 2199 Series!(IndexIterator, Iterator, N, kind) a, 2200 Series!(IndexIterator, Iterator, N, kind) b, 2201 ) @safe 2202 { 2203 import core.lifetime: move; 2204 Series!(IndexIterator, Iterator, N, kind)[2] ar = [move(a), move(b)]; 2205 return unionSeriesImplPrivate!false(move(ar)); 2206 } 2207 2208 /// ditto 2209 auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( 2210 Series!(IndexIterator, Iterator, N, kind) a, 2211 Series!(IndexIterator, Iterator, N, kind) b, 2212 Series!(IndexIterator, Iterator, N, kind) c, 2213 ) @safe 2214 { 2215 import core.lifetime: move; 2216 Series!(IndexIterator, Iterator, N, kind)[3] ar = [move(a), move(b), move(c)]; 2217 return unionSeriesImplPrivate!false(move(ar)); 2218 } 2219 2220 /// ditto 2221 auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( 2222 Series!(IndexIterator, Iterator, N, kind) a, 2223 Series!(IndexIterator, Iterator, N, kind) b, 2224 Series!(IndexIterator, Iterator, N, kind) c, 2225 Series!(IndexIterator, Iterator, N, kind) d, 2226 ) @safe 2227 { 2228 import core.lifetime: move; 2229 Series!(IndexIterator, Iterator, N, kind)[4] ar = [move(a), move(b), move(c), move(d)]; 2230 return unionSeriesImplPrivate!false(move(ar)); 2231 } 2232 2233 2234 /// 2235 @safe pure nothrow version(mir_test) unittest 2236 { 2237 import mir.date: Date; 2238 2239 ////////////////////////////////////// 2240 // Constructs two time-series. 2241 ////////////////////////////////////// 2242 auto index0 = [1,3,4]; 2243 auto data0 = [1.0, 3, 4]; 2244 auto series0 = index0.series(data0); 2245 2246 auto index1 = [1,2,5]; 2247 auto data1 = [10.0, 20, 50]; 2248 auto series1 = index1.series(data1); 2249 2250 ////////////////////////////////////// 2251 // Merges multiple series into one. 2252 ////////////////////////////////////// 2253 // Order is matter. 2254 // The first slice has higher priority. 2255 auto m0 = unionSeries(series0, series1); 2256 auto m1 = unionSeries(series1, series0); 2257 2258 assert(m0.index == m1.index); 2259 assert(m0.data == [ 1, 20, 3, 4, 50]); 2260 assert(m1.data == [10, 20, 3, 4, 50]); 2261 } 2262 2263 /// 2264 @safe pure nothrow version(mir_test) unittest 2265 { 2266 import mir.date: Date; 2267 2268 ////////////////////////////////////// 2269 // Constructs three time-series. 2270 ////////////////////////////////////// 2271 auto index0 = [1,3,4]; 2272 auto data0 = [1.0, 3, 4]; 2273 auto series0 = index0.series(data0); 2274 2275 auto index1 = [1,2,5]; 2276 auto data1 = [10.0, 20, 50]; 2277 auto series1 = index1.series(data1); 2278 2279 auto index2 = [1, 6]; 2280 auto data2 = [100.0, 600]; 2281 auto series2 = index2.series(data2); 2282 2283 ////////////////////////////////////// 2284 // Merges multiple series into one. 2285 ////////////////////////////////////// 2286 // Order is matter. 2287 // The first slice has higher priority. 2288 auto m0 = unionSeries(series0, series1, series2); 2289 auto m1 = unionSeries(series1, series0, series2); 2290 auto m2 = unionSeries(series2, series0, series1); 2291 2292 assert(m0.index == m1.index); 2293 assert(m0.index == m2.index); 2294 assert(m0.data == [ 1, 20, 3, 4, 50, 600]); 2295 assert(m1.data == [ 10, 20, 3, 4, 50, 600]); 2296 assert(m2.data == [100, 20, 3, 4, 50, 600]); 2297 } 2298 2299 /** 2300 Merges multiple (time) series into one. 2301 2302 Params: 2303 allocator = memory allocator 2304 seriesTuple = variadic static array of composed of series. 2305 Returns: sorted manually allocated series. 2306 See_also $(LREF unionSeries) 2307 */ 2308 auto makeUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator)(auto ref Allocator allocator, Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...) 2309 if (C > 1) 2310 { 2311 return unionSeriesImplPrivate!false(seriesTuple, allocator); 2312 } 2313 2314 /// 2315 @system pure nothrow version(mir_test) unittest 2316 { 2317 import std.experimental.allocator; 2318 import std.experimental.allocator.building_blocks.region; 2319 2320 ////////////////////////////////////// 2321 // Constructs two time-series. 2322 ////////////////////////////////////// 2323 auto index0 = [1,3,4]; 2324 2325 auto data0 = [1.0, 3, 4]; 2326 auto series0 = index0.series(data0); 2327 2328 auto index1 = [1,2,5]; 2329 2330 auto data1 = [10.0, 20, 50]; 2331 auto series1 = index1.series(data1); 2332 2333 ////////////////////////////////////// 2334 // Merges multiple series into one. 2335 ////////////////////////////////////// 2336 2337 InSituRegion!(1024) allocator; 2338 2339 auto m0 = allocator.makeUnionSeries(series0, series1); 2340 auto m1 = allocator.makeUnionSeries(series1, series0); // order is matter 2341 2342 assert(m0.index == m1.index); 2343 assert(m0.data == [ 1, 20, 3, 4, 50]); 2344 assert(m1.data == [10, 20, 3, 4, 50]); 2345 2346 /// series should have the same sizes as after allocation 2347 allocator.dispose(m0.index.field); 2348 allocator.dispose(m0.data.field); 2349 allocator.dispose(m1.index.field); 2350 allocator.dispose(m1.data.field); 2351 } 2352 2353 /** 2354 Merges multiple (time) series into one. 2355 2356 Returns: sorted manually allocated series. 2357 See_also $(LREF unionSeries) 2358 */ 2359 auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( 2360 Series!(IndexIterator, Iterator, N, kind) a, 2361 Series!(IndexIterator, Iterator, N, kind) b, 2362 ) @safe 2363 { 2364 import core.lifetime: move; 2365 Series!(IndexIterator, Iterator, N, kind)[2] ar = [move(a), move(b)]; 2366 return unionSeriesImplPrivate!true(move(ar)); 2367 } 2368 2369 ///ditto 2370 auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( 2371 Series!(IndexIterator, Iterator, N, kind) a, 2372 Series!(IndexIterator, Iterator, N, kind) b, 2373 Series!(IndexIterator, Iterator, N, kind) c, 2374 ) @safe 2375 { 2376 import core.lifetime: move; 2377 Series!(IndexIterator, Iterator, N, kind)[3] ar = [move(a), move(b), move(c)]; 2378 return unionSeriesImplPrivate!true(move(ar)); 2379 } 2380 2381 ///ditto 2382 auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( 2383 Series!(IndexIterator, Iterator, N, kind) a, 2384 Series!(IndexIterator, Iterator, N, kind) b, 2385 Series!(IndexIterator, Iterator, N, kind) c, 2386 Series!(IndexIterator, Iterator, N, kind) d, 2387 ) @safe 2388 { 2389 import core.lifetime: move; 2390 Series!(IndexIterator, Iterator, N, kind)[4] ar = [move(a), move(b), move(c), move(d)]; 2391 return unionSeriesImplPrivate!true(move(ar)); 2392 } 2393 2394 /// 2395 @safe pure nothrow version(mir_test) unittest 2396 { 2397 import mir.rc.array; 2398 2399 ////////////////////////////////////// 2400 // Constructs two time-series. 2401 ////////////////////////////////////// 2402 auto index0 = [1,3,4]; 2403 2404 auto data0 = [1.0, 3, 4]; 2405 auto series0 = index0.series(data0); 2406 2407 auto index1 = [1,2,5]; 2408 2409 auto data1 = [10.0, 20, 50]; 2410 auto series1 = index1.series(data1); 2411 2412 ////////////////////////////////////// 2413 // Merges multiple series into one. 2414 ////////////////////////////////////// 2415 2416 Series!(RCI!int, RCI!double) m0 = rcUnionSeries(series0, series1); 2417 Series!(RCI!int, RCI!double) m1 = rcUnionSeries(series1, series0); // order is matter 2418 2419 assert(m0.index == m1.index); 2420 assert(m0.data == [ 1, 20, 3, 4, 50]); 2421 assert(m1.data == [10, 20, 3, 4, 50]); 2422 } 2423 2424 /** 2425 Initialize preallocated series using union of multiple (time) series. 2426 Doesn't make any allocations. 2427 2428 Params: 2429 seriesTuple = dynamic array composed of series. 2430 uninitSeries = uninitialized series with exactly required length. 2431 */ 2432 pragma(inline, false) 2433 auto unionSeriesImpl(I, E, 2434 IndexIterator, Iterator, size_t N, SliceKind kind, UI, UE)( 2435 scope Series!(IndexIterator, Iterator, N, kind)[] seriesTuple, 2436 Series!(UI*, UE*, N) uninitSeries, 2437 ) @trusted 2438 { 2439 import mir.conv: emplaceRef; 2440 import mir.algorithm.setops: multiwayUnion; 2441 2442 enum N = N; 2443 alias I = DeepElementType!(typeof(seriesTuple[0].index)); 2444 alias E = DeepElementType!(typeof(seriesTuple[0].data)); 2445 2446 if(uninitSeries.length) 2447 { 2448 auto u = seriesTuple.multiwayUnion!"a.index < b.index"; 2449 do 2450 { 2451 auto obs = u.front; 2452 emplaceRef!I(uninitSeries.index.front, obs.index); 2453 static if (N == 1) 2454 emplaceRef!E(uninitSeries.data.front, obs.data); 2455 else 2456 each!(emplaceRef!E)(uninitSeries.data.front, obs.data); 2457 u.popFront; 2458 uninitSeries.popFront; 2459 } 2460 while(uninitSeries.length); 2461 } 2462 } 2463 2464 private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator...)(scope Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple, ref Allocator allocator) @safe 2465 if (C > 1 && Allocator.length <= 1) 2466 { 2467 import mir.algorithm.setops: unionLength; 2468 import mir.ndslice.topology: iota; 2469 import mir.internal.utility: Iota; 2470 import mir.ndslice.allocation: uninitSlice, makeUninitSlice; 2471 static if (rc) 2472 import mir.rc.array; 2473 2474 Slice!IndexIterator[C] indeces; 2475 foreach (i; Iota!C) 2476 indeces[i] = seriesTuple[i].index; 2477 2478 immutable len = (()@trusted => indeces[].unionLength)(); 2479 2480 alias I = typeof(seriesTuple[0].index.front); 2481 alias E = typeof(seriesTuple[0].data.front); 2482 static if (rc) 2483 alias R = Series!(RCI!I, RCI!E, N); 2484 else 2485 alias R = Series!(I*, E*, N); 2486 alias UI = Unqual!I; 2487 alias UE = Unqual!E; 2488 2489 static if (N > 1) 2490 { 2491 auto shape = seriesTuple[0].data._lengths; 2492 shape[0] = len; 2493 2494 foreach (ref sl; seriesTuple[1 .. $]) 2495 foreach (i; Iota!(1, N)) 2496 if (seriesTuple.data[0]._lengths[i] != sl.data._lengths[i]) 2497 assert(0, "shapes mismatch"); 2498 } 2499 else 2500 { 2501 alias shape = len; 2502 } 2503 2504 static if (rc == false) 2505 { 2506 static if (Allocator.length) 2507 auto ret = (()@trusted => allocator[0].makeUninitSlice!UI(len).series(allocator[0].makeUninitSlice!UE(shape)))(); 2508 else 2509 auto ret = (()@trusted => len.uninitSlice!UI.series(shape.uninitSlice!UE))(); 2510 } 2511 else 2512 { 2513 static if (Allocator.length) 2514 static assert(0, "rcUnionSeries with allocators is not implemented."); 2515 else 2516 auto ret = (()@trusted => 2517 len 2518 .mininitRcarray!UI 2519 .asSlice 2520 .series( 2521 shape 2522 .iota 2523 .elementCount 2524 .mininitRcarray!UE 2525 .asSlice 2526 .sliced(shape)))(); 2527 } 2528 2529 static if (C == 2) // fast path 2530 { 2531 alias algo = troykaSeriesImpl!( 2532 ref (scope ref key, scope return ref left) => left, 2533 ref (scope ref key, scope return ref left, scope return ref right) => left, 2534 ref (scope ref key, scope return ref right) => right, 2535 ); 2536 algo!(I, E)(seriesTuple[0], seriesTuple[1], ret.lightScope); 2537 } 2538 else 2539 { 2540 unionSeriesImpl!(I, E)((()@trusted => seriesTuple[])(), ret.lightScope); 2541 } 2542 2543 return () @trusted {return *cast(R*) &ret; }(); 2544 } 2545 2546 /** 2547 Inserts or assigns a series to the associative array `aa`. 2548 Params: 2549 aa = associative array 2550 series = series 2551 Returns: 2552 associative array 2553 */ 2554 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) @property 2555 { 2556 auto s = series.lightScope; 2557 foreach (i; 0 .. s.length) 2558 { 2559 aa[s.index[i]] = s.data[i]; 2560 } 2561 return aa; 2562 } 2563 2564 /// 2565 @safe pure nothrow version(mir_test) unittest 2566 { 2567 auto a = [1: 3.0, 4: 2.0]; 2568 auto s = series([1, 2, 3], [10, 20, 30]); 2569 a.insertOrAssign = s; 2570 assert(a.series == series([1, 2, 3, 4], [10.0, 20, 30, 2])); 2571 } 2572 2573 /** 2574 Inserts a series to the associative array `aa`. 2575 Params: 2576 aa = associative array 2577 series = series 2578 Returns: 2579 associative array 2580 */ 2581 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) @property 2582 { 2583 auto s = series.lightScope; 2584 foreach (i; 0 .. s.length) 2585 { 2586 if (s.index[i] in aa) 2587 continue; 2588 aa[s.index[i]] = s.data[i]; 2589 } 2590 return aa; 2591 } 2592 2593 /// 2594 @safe pure nothrow version(mir_test) unittest 2595 { 2596 auto a = [1: 3.0, 4: 2.0]; 2597 auto s = series([1, 2, 3], [10, 20, 30]); 2598 a.insert = s; 2599 assert(a.series == series([1, 2, 3, 4], [3.0, 20, 30, 2])); 2600 }