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