UNPKG

31.3 kBJavaScriptView Raw
1import { Mx, Ob, Samples, clone, Ar, Pivot, PivotModes } from 'veho';
2import { Comparer, StatMx } from 'borel';
3import { totx } from 'xbrief';
4import { Num, Typ } from 'typen';
5
6function _defineProperty(obj, key, value) {
7 if (key in obj) {
8 Object.defineProperty(obj, key, {
9 value: value,
10 enumerable: true,
11 configurable: true,
12 writable: true
13 });
14 } else {
15 obj[key] = value;
16 }
17
18 return obj;
19}
20
21function _classPrivateMethodGet(receiver, privateSet, fn) {
22 if (!privateSet.has(receiver)) {
23 throw new TypeError("attempted to get private field on non-instance");
24 }
25
26 return fn;
27}
28
29/**
30 * If y >= 0 then sort rows, else (e.g. y===undefined) sort keys.
31 * @param {*[]} keys
32 * @param {*[][]} rows
33 * @param {function(*,*):number} comparer
34 * @param {number} [y]
35 * @returns {[*[], *[][]]}
36 */
37const sortAlong = (keys, rows, comparer, y) => {
38 const comp = (a, b) => comparer(a[0], b[0]),
39 ks = Array(keys.length),
40 rs = y >= 0 ? rows.map((r, i) => [r[y], keys[i], r]).sort(comp).map(([, k, r], i) => {
41 ks[i] = k;
42 return r;
43 }) : rows.map((r, i) => [keys[i], r]).sort(comp).map(([k, r], i) => {
44 ks[i] = k;
45 return r;
46 });
47
48 return [ks, rs];
49};
50
51class CrosTab {
52 /**
53 *
54 * @param {*[]} side
55 * @param {*[]} banner
56 * @param {*[][]} matrix
57 * @param {string} [title]
58 */
59 constructor(side, banner, matrix, title) {
60 _defineProperty(this, "side", void 0);
61
62 _defineProperty(this, "banner", void 0);
63
64 _defineProperty(this, "matrix", void 0);
65
66 _defineProperty(this, "title", void 0);
67
68 this.side = side;
69 this.banner = banner;
70 this.matrix = matrix;
71 this.title = title || '';
72 }
73 /**
74 * Shallow copy
75 * @param {*[]} side
76 * @param {*[]} banner
77 * @param {*[][]} matrix
78 * @param {string} [title]
79 * @return {CrosTab}
80 */
81
82
83 static from({
84 side,
85 banner,
86 matrix,
87 title
88 }) {
89 return new CrosTab(side, banner, matrix, title);
90 }
91 /**
92 * Shallow copy
93 * @param {*[]} side
94 * @param {*[]} banner
95 * @param {function(number,number):*} func
96 * @param {string} [title]
97 * @return {CrosTab}
98 */
99
100
101 static ini({
102 side,
103 banner,
104 func,
105 title
106 }) {
107 const matrix = Mx.ini(side === null || side === void 0 ? void 0 : side.length, banner === null || banner === void 0 ? void 0 : banner.length, (x, y) => func(x, y));
108 return CrosTab.from({
109 side,
110 banner,
111 matrix,
112 title
113 });
114 }
115 /**
116 *
117 * @param {*[]|boolean} [side]
118 * @param {*[]|boolean} [banner]
119 * @returns {*}
120 */
121
122
123 toSamples({
124 side,
125 banner
126 }) {
127 const [sideIsArr, bannerIsArr] = [Array.isArray(side) && side.length || side === true, Array.isArray(banner) && banner.length || banner === true];
128
129 if (sideIsArr) {
130 if (bannerIsArr) {
131 var _side$map;
132
133 const entries = banner.map(x => [x, this.coin(x)]);
134 return (_side$map = side.map(x => this.matrix[this.roin(x)]), Mx.transpose(_side$map)).map(col => Ob.fromEntries(entries, i => col[i]));
135 } else {
136 var _this$matrix;
137
138 const entries = side.map(x => [x, this.roin(x)]);
139 return (_this$matrix = this.matrix, Mx.transpose(_this$matrix)).map(col => Ob.fromEntries(entries, i => col[i]));
140 }
141 } else {
142 if (bannerIsArr) {
143 const entries = banner.map(x => [x, this.coin(x)]);
144 return this.matrix.map(row => Ob.fromEntries(entries, i => row[i]));
145 } else {
146 return Samples.fromCrosTab(this);
147 }
148 }
149 }
150
151 get toJson() {
152 var _this$matrix2;
153
154 return {
155 side: this.side.slice(),
156 banner: this.banner.slice(),
157 matrix: (_this$matrix2 = this.matrix, clone(_this$matrix2)),
158 title: this.title
159 };
160 }
161
162 get columns() {
163 return Mx.transpose(this.matrix);
164 }
165 /**
166 *
167 * @param {*[][]} [newMatrix]
168 * @param {*[]} [newSide]
169 * @param {*[]} [newBanner]
170 * @returns {CrosTab}
171 */
172
173
174 reboot(newMatrix, newSide, newBanner) {
175 if (newMatrix) this.matrix = newMatrix;
176 if (newSide) this.side = newSide;
177 if (newBanner) this.banner = newBanner;
178 return this;
179 }
180 /**
181 * Shallow copy
182 * @param {*[][]} [newMatrix]
183 * @param {*[]} [newSide]
184 * @param {*[]} [newBanner]
185 * @return {CrosTab}
186 */
187
188
189 clone(newMatrix, newSide, newBanner) {
190 return new CrosTab(newSide || this.side.slice(), newBanner || this.banner.slice(), newMatrix || Mx.clone(this.matrix), this.title);
191 }
192
193 get size() {
194 return Mx.size(this.matrix);
195 }
196
197 get ht() {
198 var _this$side;
199
200 return (_this$side = this.side) === null || _this$side === void 0 ? void 0 : _this$side.length;
201 }
202
203 get wd() {
204 var _this$banner;
205
206 return (_this$banner = this.banner) === null || _this$banner === void 0 ? void 0 : _this$banner.length;
207 }
208
209 cell(x, y) {
210 return this.matrix[x][y];
211 }
212
213 queryCell(r, c) {
214 const x = this.side.indexOf(r),
215 y = this.banner.indexOf(c);
216 return x < 0 || y < 0 ? null : this.matrix[x][y];
217 }
218
219 queryCoordinate(r, c) {
220 return {
221 x: this.side.indexOf(r),
222 y: this.banner.indexOf(c)
223 };
224 }
225
226 roin(r) {
227 return Number.isInteger(r) && 0 <= r && r < this.ht ? r : this.side.indexOf(r);
228 }
229
230 coin(c) {
231 return Number.isInteger(c) && 0 <= c && c < this.wd ? c : this.banner.indexOf(c);
232 }
233
234 row(r) {
235 const x = this.roin(r);
236 return this.matrix[x];
237 }
238
239 column(c) {
240 const y = this.coin(c);
241 return this.matrix.map(row => row[y]);
242 }
243
244 setRow(r, newRow) {
245 const x = this.roin(r);
246 this.matrix[x] = newRow;
247 return this;
248 }
249
250 setColumn(c, newColumn) {
251 const y = this.coin(c);
252 this.matrix.forEach((row, i) => {
253 row[y] = newColumn[i];
254 });
255 return this;
256 }
257
258 map(fn, {
259 mutate = true
260 } = {}) {
261 const matrix = Mx.map(this.matrix, fn);
262 return mutate ? this.reboot(matrix) : this.clone(matrix);
263 }
264 /**
265 * Push row into this and return this.
266 * No shallow nor deep copy of the matrix.
267 * @param {*} field
268 * @param {*[]} row
269 * @return {CrosTab}
270 */
271
272
273 pushRow(field, row) {
274 this.side.push(field);
275 this.matrix.push(row);
276 return this;
277 }
278 /**
279 * Unshift row into this and return this.
280 * No shallow nor deep copy of the matrix.
281 * @param {*} field
282 * @param {*[]} row
283 * @return {CrosTab}
284 */
285
286
287 unshiftRow(field, row) {
288 this.side.unshift(field);
289 this.matrix.unshift(row);
290 return this;
291 }
292 /**
293 * Push column into this and return this.
294 * No shallow nor deep copy of the matrix.
295 * @param {*} field
296 * @param {*[]} col
297 * @return {CrosTab}
298 */
299
300
301 pushCol(field, col) {
302 this.banner.push(field);
303 this.matrix.forEach((row, i) => row.push(col[i]));
304 return this;
305 }
306 /**
307 * Unshift column into this and return this.
308 * No shallow nor deep copy of the matrix.
309 * @param {*} field
310 * @param {*[]} col
311 * @return {CrosTab}
312 */
313
314
315 unshiftCol(field, col) {
316 this.banner.unshift(field);
317 this.matrix.forEach((row, i) => row.unshift(col[i]));
318 return this;
319 }
320
321 select({
322 side: s,
323 banner: b
324 } = {}, {
325 mutate = true
326 } = {}) {
327 let {
328 matrix,
329 side,
330 banner
331 } = this;
332
333 if (s && s.length) {
334 const roins = s.map(x => this.roin(x));
335 matrix = Ar.select(matrix, roins);
336 side = s;
337 }
338
339 if (b && b.length) {
340 const coins = b.map(x => this.coin(x));
341 matrix = Mx.select(matrix, coins);
342 banner = b;
343 }
344
345 return mutate ? this.reboot(matrix, side, banner) : this.clone(matrix, side, banner);
346 }
347
348 slice({
349 rows = {
350 begin: undefined,
351 end: undefined
352 },
353 cols = {
354 begin: undefined,
355 end: undefined
356 }
357 }, {
358 mutate = true
359 } = {}) {
360 let {
361 side,
362 banner,
363 matrix
364 } = this;
365 let begin, end;
366
367 if (rows) {
368 ({
369 begin,
370 end
371 } = rows);
372 side = side.slice(begin, end);
373 matrix = matrix.slice(begin, end);
374 }
375
376 if (cols) {
377 ({
378 begin,
379 end
380 } = cols);
381 banner = this.banner.slice(begin, end);
382 matrix = matrix.map(row => row.slice(begin, end));
383 }
384
385 return mutate ? this.reboot(matrix, side, banner) : this.clone(matrix, side, banner);
386 }
387
388 sort(by = 'rows', field, comparer, {
389 mutate = true
390 } = {}) {
391 var _matrix, _matrix2;
392
393 let {
394 side,
395 banner,
396 matrix
397 } = this;
398
399 switch (by.charAt(0)) {
400 case 'c':
401 const x = this.roin(field);
402 [banner, matrix] = sortAlong(banner, (_matrix = matrix, Mx.transpose(_matrix)), comparer, x);
403 matrix = (_matrix2 = matrix, Mx.transpose(_matrix2));
404 break;
405
406 case 'r':
407 default:
408 const y = this.coin(field);
409 [side, matrix] = sortAlong(side, matrix, comparer, y);
410 break;
411 }
412
413 return mutate ? this.reboot(matrix, side, banner) : this.clone(matrix, side, banner);
414 }
415
416 sortLabel(by = 'rows', comparer) {
417 var _matrix3, _matrix4;
418
419 let {
420 side,
421 banner,
422 matrix,
423 title
424 } = this;
425
426 switch (by.charAt(0)) {
427 case 'c':
428 side = side.slice();
429 [banner, matrix] = sortAlong(banner, (_matrix3 = matrix, Mx.transpose(_matrix3)), comparer);
430 matrix = (_matrix4 = matrix, Mx.transpose(_matrix4));
431 break;
432
433 case 'r':
434 default:
435 banner = banner.slice();
436 [side, matrix] = sortAlong(side, matrix, comparer);
437 break;
438 }
439
440 return new CrosTab(side, banner, matrix, title);
441 }
442
443 transpose(newTitle, {
444 mutate = true
445 } = {}) {
446 const {
447 banner: side,
448 side: banner,
449 columns: matrix
450 } = this;
451 return mutate ? this.reboot(matrix, side, banner) : this.clone(matrix, side, banner);
452 }
453
454}
455
456const {
457 inferData
458} = Num;
459
460class ArrTyp {
461 /**
462 *
463 * @param {*[]} column
464 * @return {string|unknown}
465 */
466 static infer(column) {
467 if (column.length) {
468 const types = column.map(inferData);
469 const dist = new Set(types);
470
471 switch (dist.size) {
472 case 1:
473 return types[0];
474
475 case 2:
476 return dist.has('number') && dist.has('numstr') ? 'numstr' : 'misc';
477
478 default:
479 return 'misc';
480 }
481 } else {
482 return 'null';
483 }
484 }
485
486}
487
488/**
489 *
490 * @type {{intersect: number, left: number, union: number, right: number}}
491 */
492const JoinT = {
493 intersect: -1,
494 union: 0,
495 left: 1,
496 right: 2
497};
498
499const {
500 spliceCols,
501 size
502} = Mx;
503const {
504 select,
505 ini: ar
506} = Ar;
507
508const locInd = (mx, ys, vs, hi) => mx.findIndex(row => {
509 for (let i = 0; i < hi; i++) if (row[ys[i]] !== vs[i]) return false;
510
511 return true;
512});
513/**
514 *
515 * @param joinT
516 * @returns {
517 * (function({mx: *[][], ys: number[]}, {mx: *[][], ys: number[]}): *[][])
518 * |(function({mx: *[][], ys: number[]}, {mx: *[][], ys: number[]}, *=): *[][])
519 * }
520 */
521
522
523const joiner = joinT => {
524 switch (joinT) {
525 case JoinT.union:
526 return MxJoin.joinUnion;
527
528 case JoinT.left:
529 return MxJoin.joinLeft;
530
531 case JoinT.right:
532 return MxJoin.joinRight;
533
534 case JoinT.intersect:
535 default:
536 return MxJoin.joinIntersect;
537 }
538};
539
540class MxJoin {}
541
542_defineProperty(MxJoin, "joinUnion", ({
543 mx: mxL,
544 ys: ysL
545}, {
546 mx: mxR,
547 ys: ysR
548}, fillEmpty) => {
549 const hL = mxL.length,
550 hR = mxR.length,
551 hi = ysR.length,
552 mx = Array(hL),
553 mxL2 = spliceCols(Mx.copy(mxL), ysL.slice().sort(Comparer.numberAscending)),
554 [, wL] = size(mxL2),
555 mxR2 = spliceCols(Mx.copy(mxR), ysR.slice().sort(Comparer.numberAscending)),
556 [, wR] = size(mxR2),
557 idxRiLs = new Set();
558
559 for (let i = 0, x, vsL; i < hL; i++) {
560 vsL = select(mxL[i], ysL, hi);
561 x = locInd(mxR, ysR, vsL, hi);
562
563 if (x < 0) {
564 mx[i] = vsL.concat(mxL2[i], ar(wR, fillEmpty));
565 } else {
566 mx[i] = vsL.concat(mxL2[i], mxR2[x]);
567 idxRiLs.add(x);
568 }
569 }
570
571 for (let i = 0, x, vsR; i < hR; i++) {
572 var _vsR$concat;
573
574 if (idxRiLs.has(i)) {
575 continue;
576 }
577
578 vsR = select(mxR[i], ysR, hi);
579 x = locInd(mxL, ysL, vsR, hi);
580 if (x < 0) _vsR$concat = vsR.concat(ar(wL, fillEmpty), mxR2[i]), mx.push(_vsR$concat);
581 }
582
583 return mx;
584});
585
586_defineProperty(MxJoin, "joinLeft", ({
587 mx: mxL,
588 ys: ysL
589}, {
590 mx: mxR,
591 ys: ysR
592}, fillEmpty) => {
593 const hL = mxL.length,
594 hi = ysL.length,
595 mx = Array(hL),
596 mxL2 = spliceCols(Mx.copy(mxL), ysL.slice().sort(Comparer.numberAscending)),
597 mxR2 = spliceCols(Mx.copy(mxR), ysR.slice().sort(Comparer.numberAscending)),
598 [, wR] = size(mxR2);
599
600 for (let i = 0, x, vsL; i < hL; i++) {
601 vsL = select(mxL[i], ysL, hi);
602 x = locInd(mxR, ysR, vsL, hi);
603 mx[i] = x < 0 ? vsL.concat(mxL2[i], ar(wR, fillEmpty)) : vsL.concat(mxL2[i], mxR2[x]);
604 }
605
606 return mx;
607});
608
609_defineProperty(MxJoin, "joinRight", ({
610 mx: mxL,
611 ys: ysL
612}, {
613 mx: mxR,
614 ys: ysR
615}, fillEmpty) => {
616 const hR = mxR.length,
617 hi = ysR.length,
618 mx = Array(hR),
619 mxL2 = spliceCols(Mx.copy(mxL), ysL.slice().sort(Comparer.numberAscending)),
620 [, wL] = size(mxL2),
621 mxR2 = spliceCols(Mx.copy(mxR), ysR.slice().sort(Comparer.numberAscending));
622
623 for (let i = 0, x, vsR; i < hR; i++) {
624 vsR = select(mxR[i], ysR, hi);
625 x = locInd(mxL, ysL, vsR, hi);
626 mx[i] = x < 0 ? vsR.concat(ar(wL, fillEmpty), mxR2[i]) : vsR.concat(mxL2[x], mxR2[i]);
627 }
628
629 return mx;
630});
631
632_defineProperty(MxJoin, "joinIntersect", ({
633 mx: mxL,
634 ys: ysL
635}, {
636 mx: mxR,
637 ys: ysR
638}) => {
639 const hL = mxL.length,
640 hi = ysL.length,
641 mx = [],
642 mxL2 = spliceCols(Mx.copy(mxL), ysL.slice().sort(Comparer.numberAscending)),
643 mxR2 = spliceCols(Mx.copy(mxR), ysR.slice().sort(Comparer.numberAscending));
644
645 for (let i = 0, x, vsL; i < hL; i++) {
646 vsL = select(mxL[i], ysL, hi);
647 x = locInd(mxR, ysR, vsL, hi);
648 if (x < 0) continue;
649 mx.push(vsL.concat(mxL2[i], mxR2[x]));
650 }
651
652 return mx;
653});
654
655const pivotMode = stat => {
656 return typeof stat === 'string' ? PivotModes[stat] || (stat === 'cnt' ? PivotModes.count : PivotModes.array) : PivotModes.array;
657};
658
659Pivot.prototype.pivotStat = function ([x, y], [index, stat], {
660 boot
661} = {}) {
662 var _stat;
663
664 const mode = (_stat = stat, pivotMode(_stat));
665 let {
666 side,
667 banner,
668 matrix
669 } = this.pivot([x, y, index], {
670 mode,
671 boot
672 });
673 if (mode === PivotModes.array) matrix = Mx.map(matrix, stat);
674 return {
675 side,
676 banner,
677 matrix
678 };
679};
680/**
681 *
682 * @param {number} x
683 * @param {number} y
684 * @param {{index:number,stat:string|function(*[]):number}[]}fields
685 */
686
687
688Pivot.prototype.pivotMatrices = function ([x, y], fields) {
689 const hi = fields.length;
690 let {
691 side,
692 banner,
693 matrix
694 } = this.pivotStat([x, y], fields[0], {
695 boot: true
696 });
697 const matrices = [matrix];
698
699 for (let i = 1; i < hi; i++) {
700 var _this$pivotStat$matri;
701
702 _this$pivotStat$matri = this.pivotStat([x, y], fields[i], {
703 boot: false
704 }).matrix, matrices.push(_this$pivotStat$matri);
705 }
706
707 return {
708 side,
709 banner,
710 matrix: Mx.zip(matrices)
711 };
712};
713/**
714 *
715 * @param {Table} table
716 * @param {TableSpec} spec
717 * @param {string} spec.side side
718 * @param {string} spec.banner banner
719 * @param {Object<string|number,function(*):boolean>} spec.filter
720 * @param {string|string[]|Object<string|number,function(*[]):number>} spec.cell
721 * @param {function():number} spec.calc - (field1,field2,...)=>number
722 * @returns {CrosTab}
723 */
724
725
726const crosTabFut = (table, spec) => {
727 var _spec$side, _spec$banner, _field;
728
729 // spec |> console.log
730 table = table.filter(spec.filter, {
731 mutate: false
732 });
733 const pvt = new Pivot(table.matrix),
734 coin = table.coin.bind(table),
735 cellEntries = Object.entries(spec.cell),
736 [x, y] = [(_spec$side = spec.side, coin(_spec$side)), (_spec$banner = spec.banner, coin(_spec$banner))];
737 let side, banner, matrix;
738
739 switch (cellEntries.length) {
740 case 0:
741 ({
742 side,
743 banner,
744 matrix
745 } = pvt.pivot([x, y], {
746 mode: PivotModes.count
747 }));
748 break;
749
750 case 1:
751 const [[field, stat]] = cellEntries;
752 ({
753 side,
754 banner,
755 matrix
756 } = pvt.pivotStat([x, y], [(_field = field, coin(_field)), stat]));
757 break;
758
759 default:
760 ({
761 side,
762 banner,
763 matrix
764 } = pvt.pivotMulti([x, y], cellEntries.map(([field, stat]) => {
765 var _field2;
766
767 return [(_field2 = field, coin(_field2)), stat];
768 })));
769 }
770
771 const {
772 calc
773 } = spec;
774
775 if (calc) {
776 const fn = ar => calc.apply(null, ar);
777
778 matrix = Mx.map(matrix, fn);
779 }
780
781 return CrosTab.from({
782 side,
783 banner,
784 matrix,
785 spec: spec.title
786 });
787};
788
789class Table {
790 /**
791 *
792 * @param {*[]} [banner]
793 * @param {*[][]} [matrix]
794 * @param {string} [title]
795 * @param {*[]} [types]
796 */
797 constructor(_banner, matrix, title, types) {
798 _fieldIndexes.add(this);
799
800 this.banner = _banner || [];
801 this.matrix = matrix || [[]];
802 this.title = title || '';
803 this.types = types;
804 }
805 /**
806 *
807 * @returns {[]|Array}
808 */
809
810
811 get types() {
812 return this._types;
813 }
814 /**
815 *
816 * @param {Array} [value]
817 */
818
819
820 set types(value) {
821 this._types = Ar.isEmpty(value) ? [] : value;
822 }
823 /**
824 *
825 * @param {*[]} [banner]
826 * @param {*[][]} [matrix]
827 * @param {string} [title]
828 * @param {string[]} [types]
829 * @return {Table}
830 */
831
832
833 static from({
834 banner,
835 matrix,
836 title,
837 types
838 }) {
839 return new Table(banner, matrix, title, types);
840 }
841 /**
842 *
843 * @param {string|number|[*,*]} [fields]
844 * @returns {Object<*, *>[]}
845 */
846
847
848 toSamples(fields) {
849 if (!(fields === null || fields === void 0 ? void 0 : fields.length)) return this.matrix.map(row => Ob.ini(this.banner, row));
850 const entries = fields.map(x => Array.isArray(x) ? [[x[1]], this.coin(x[0])] : [x, this.coin(x)]);
851 return this.matrix.map(row => Ob.fromEntries(entries, i => row[i]));
852 }
853 /**
854 *
855 * @param {boolean} [mutate=true]
856 * @returns {*}
857 */
858
859
860 toJson(mutate = true) {
861 var _matrix;
862
863 const {
864 banner,
865 matrix,
866 title
867 } = this;
868 return mutate ? {
869 banner,
870 matrix,
871 title
872 } : {
873 banner: banner.slice(),
874 matrix: (_matrix = matrix, Mx.clone(_matrix)),
875 title: title.slice()
876 };
877 }
878 /**
879 *
880 * @param {{}[]} samples
881 * @param {*[]|[*,*][]} [fields]
882 * @param {string} [title]
883 * @param {*[]} [types]
884 * @return {Table}
885 */
886
887
888 static fromSamples(samples, {
889 fields,
890 title,
891 types
892 } = {}) {
893 const {
894 head,
895 rows
896 } = Samples.toTable(samples, {
897 fields
898 });
899 return new Table(head, rows, title, types);
900 }
901 /**
902 * Return 'this' by loading a new matrix
903 * @param {*[][]} newMx
904 * @param {*[]} [newBanner]
905 * @param {string[]} [newTypes]
906 * @return {Table}
907 */
908
909
910 reboot(newMx, newBanner, newTypes) {
911 this.matrix = newMx;
912 if (newBanner) this.banner = newBanner;
913 if (newTypes) this.types = newTypes;
914 return this;
915 }
916 /**
917 *
918 * @param {*[][]} [newMx]
919 * @param {*[]} [newBanner]
920 * @param {*[]} [newTypes]
921 * @return {Table}
922 */
923
924
925 clone(newMx, newBanner, newTypes) {
926 var _this$matrix;
927
928 return new Table(newBanner || this.banner.slice(), newMx || (_this$matrix = this.matrix, Mx.clone(_this$matrix)), this.title, newTypes || this.types.slice());
929 }
930 /**
931 *
932 * @param {*[]|[*,*][]} fields
933 * @returns {{indexes: [], banner: []}|{indexes: *, banner: *}}
934 */
935
936
937 /**
938 *
939 * @param {*[]|[*,*][]} fields
940 * @param {boolean=true} [mutate]
941 * @returns {Table}
942 */
943 select(fields, {
944 mutate = true
945 } = {}) {
946 if (!(fields === null || fields === void 0 ? void 0 : fields.length)) return mutate ? this : this.clone();
947
948 const {
949 indexes,
950 banner
951 } = _classPrivateMethodGet(this, _fieldIndexes, _fieldIndexes2).call(this, fields);
952
953 const matrix = Mx.select(this.matrix, indexes);
954 return mutate ? this.reboot(matrix, banner) : this.clone(matrix, banner);
955 }
956 /**
957 *
958 * @param {*[]|[*,*][]} fields
959 * @param {boolean=true} [mutate]
960 * @returns {Table}
961 */
962
963
964 spliceColumns(fields, {
965 mutate = true
966 } = {}) {
967 var _matrix2;
968
969 const ys = fields.map(c => this.coin(c)).sort(Comparer.numberAscending),
970 {
971 matrix,
972 banner
973 } = this;
974 return mutate ? this.reboot(Mx.spliceCols(matrix, ys), Ar.splices(banner, ys)) : this.clone(Mx.spliceCols((_matrix2 = matrix, Mx.copy(_matrix2)), ys), Ar.splices(banner.slice(), ys));
975 }
976
977 map(fn, {
978 mutate = true
979 } = {}) {
980 let {
981 matrix
982 } = this;
983 matrix = Mx.map(matrix, fn);
984 return mutate ? this.reboot(matrix) : this.clone(matrix);
985 }
986
987 mapBanner(fn, {
988 mutate = true
989 } = {}) {
990 let {
991 banner,
992 matrix
993 } = this;
994 banner = banner.map(fn);
995 return mutate ? this.reboot(matrix, banner) : this.clone(matrix, banner);
996 }
997 /**
998 *
999 * @param {Table} another
1000 * @param {string[]|number[]} indexFields
1001 * @param {number} joinType - union:0,left:1,right:2,intersect:-1
1002 * @param {*} [fillEmpty]
1003 * @returns {Table}
1004 */
1005
1006
1007 join(another, indexFields, joinType = JoinT.intersect, fillEmpty = null) {
1008 return TableJoin.join(this, another, indexFields, joinType, fillEmpty);
1009 }
1010
1011 get size() {
1012 return Mx.size(this.matrix);
1013 }
1014
1015 get ht() {
1016 var _this$matrix2;
1017
1018 return (_this$matrix2 = this.matrix) === null || _this$matrix2 === void 0 ? void 0 : _this$matrix2.length;
1019 }
1020
1021 get wd() {
1022 var _this$banner;
1023
1024 return (_this$banner = this.banner) === null || _this$banner === void 0 ? void 0 : _this$banner.length;
1025 }
1026
1027 get columns() {
1028 return Mx.transpose(this.matrix);
1029 } // set types (value) {
1030 //
1031 // }
1032
1033
1034 cell(x, y) {
1035 return this.matrix[x][y];
1036 }
1037 /**
1038 *
1039 * @param {string|number} c
1040 */
1041
1042
1043 coin(c) {
1044 return Number.isInteger(c) && 0 <= c && c < this.wd ? c : this.banner.indexOf(c);
1045 }
1046
1047 column(c) {
1048 const y = this.coin(c);
1049 return this.matrix.map(row => row[y]);
1050 }
1051
1052 setColumn(c, newColumn) {
1053 const y = this.coin(c),
1054 {
1055 ht,
1056 matrix
1057 } = this;
1058
1059 for (let i = 0; i < ht; i++) matrix[i][y] = newColumn[i];
1060
1061 return this;
1062 }
1063 /**
1064 *
1065 * @param {string|number} c
1066 * @param {function} ject
1067 */
1068
1069
1070 setColumnBy(c, ject) {
1071 const y = this.coin(c);
1072
1073 for (let row of this.matrix) row[y] = ject(row[y]);
1074
1075 return this;
1076 }
1077 /**
1078 * Push row into this and return this.
1079 * No shallow nor deep copy of the matrix.
1080 * @param {*[]} row
1081 * @return {Table}
1082 */
1083
1084
1085 pushRow(row) {
1086 this.matrix.push(row);
1087 return this;
1088 }
1089 /**
1090 * Unshift row into this and return this.
1091 * No shallow nor deep copy of the matrix.
1092 * @param {*[]} row
1093 * @return {Table}
1094 */
1095
1096
1097 unshiftRow(row) {
1098 this.matrix.unshift(row);
1099 return this;
1100 }
1101 /**
1102 * Push column into this and return this.
1103 * No shallow nor deep copy of the matrix.
1104 * @param {*} name
1105 * @param {*[]} col
1106 * @return {Table}
1107 */
1108
1109
1110 pushCol(name, col) {
1111 this.banner.push(name);
1112 this.matrix.forEach((row, i) => row.push(col[i]));
1113 return this;
1114 }
1115 /**
1116 * Unshift column into this and return this.
1117 * No shallow nor deep copy of the matrix.
1118 * @param {*} name
1119 * @param {*[]} col
1120 * @return {Table}
1121 */
1122
1123
1124 unshiftCol(name, col) {
1125 this.banner.unshift(name);
1126 this.matrix.forEach((row, i) => row.unshift(col[i]));
1127 return this;
1128 }
1129 /**
1130 *
1131 * Specify the type of a column. No return
1132 * @param {string|number} field accept both column name in string or column index in integer
1133 * @param {string} typeName string | (number, float) | integer | boolean
1134 */
1135
1136
1137 changeType(field, typeName) {
1138 const y = this.coin(field);
1139 let column = Mx.column(this.matrix, y);
1140
1141 switch (typeName) {
1142 case 'string':
1143 column = column.map(totx);
1144 break;
1145
1146 case 'number':
1147 case 'float':
1148 column = column.map(Number.parseFloat);
1149 break;
1150
1151 case 'integer':
1152 column = column.map(Number.parseInt);
1153 break;
1154
1155 case 'boolean':
1156 column = column.map(Boolean);
1157 break;
1158
1159 default:
1160 typeName = this._types[y];
1161 break;
1162 }
1163
1164 this.setColumn(field, column);
1165 this._types[y] = typeName;
1166 return this;
1167 }
1168 /**
1169 * Re-generate this._types based on DPTyp.inferArr method.
1170 * Cautious: This method will change all elements of this._types.
1171 * @return {string[]}
1172 */
1173
1174
1175 inferTypes() {
1176 const {
1177 infer
1178 } = ArrTyp;
1179 this.types = this.columns.map(infer);
1180
1181 for (let [i, typ] of this.types.entries()) {
1182 switch (typ) {
1183 case 'numstr':
1184 this.changeType(i, 'number');
1185 break;
1186
1187 case 'misc':
1188 this.changeType(i, 'string');
1189 break;
1190 }
1191 }
1192
1193 return this.types;
1194 }
1195 /**
1196 *
1197 * @param {Object<string|number,function(*):boolean>} filters
1198 * @param {boolean} [mutate=true]
1199 * @return {Table}
1200 */
1201
1202
1203 filter(filters, {
1204 mutate = true
1205 } = {}) {
1206 let mx = this.matrix,
1207 y;
1208
1209 for (let [field, crit] of Object.entries(filters)) {
1210 y = this.coin(field);
1211 if (y >= 0) mx = mx.filter(row => crit(row[y]));
1212 }
1213
1214 return mutate ? this.reboot(mx) : this.clone(mx);
1215 }
1216
1217 distinct(fields, {
1218 mutate = true
1219 } = {}) {
1220 let mx = this.matrix;
1221
1222 for (let field of fields) mx = StatMx.distinct(mx, this.coin(field));
1223
1224 return mutate ? this.reboot(mx) : this.clone(mx);
1225 }
1226 /**
1227 *
1228 * @param {string|number} field
1229 * @param {boolean} [count=false]
1230 * @param {string|boolean} [sort=false] - When sort is function, sort must be a comparer between two point element.
1231 * @returns {[any, any][]|[]|any[]|*}
1232 */
1233
1234
1235 distinctCol(field, {
1236 count = false,
1237 sort = false
1238 } = {}) {
1239 const y = this.coin(field);
1240 return StatMx.distinctCol(this.matrix, y, {
1241 count,
1242 sort
1243 });
1244 }
1245
1246 sort(field, comparer, {
1247 mutate = true
1248 } = {}) {
1249 const y = this.coin(field),
1250 comp = (a, b) => comparer(a[y], b[y]),
1251 mx = this.matrix.slice().sort(comp);
1252
1253 return mutate ? this.reboot(mx) : this.clone(mx);
1254 }
1255 /**
1256 *
1257 * @param {function(*,*):number} comparer - Comparer of banner elements
1258 * @param {boolean} mutate
1259 * @returns {Table|*}
1260 */
1261
1262
1263 sortLabel(comparer, {
1264 mutate = true
1265 } = {}) {
1266 var _columns, _columns2;
1267
1268 let {
1269 banner,
1270 columns
1271 } = this;
1272 [banner, columns] = sortAlong(banner, columns, comparer);
1273 return mutate ? this.reboot((_columns = columns, Mx.transpose(_columns)), banner) : this.clone((_columns2 = columns, Mx.transpose(_columns2)), banner);
1274 }
1275 /**
1276 *
1277 * @param {TableSpec} spec
1278 * @param {string} spec.side side
1279 * @param {string} spec.banner banner
1280 * @param {{field:string, crit:function(*):boolean}[]} spec.filter
1281 * @param {{field:string, stat:function([]):number}[]} spec.cell
1282 * @param {function({}):number} spec.calc - ({col1,col2,...})=>number
1283 * @returns {CrosTab}
1284 */
1285
1286
1287 crosTab(spec) {
1288 return crosTabFut(this, spec);
1289 }
1290 /**
1291 *
1292 * @param {TableSpec} spec
1293 * @param {string} spec.side side
1294 * @param {string} spec.banner banner
1295 * @param {{field:string, crit:function(*):boolean}[]} spec.filter
1296 * @param {{field:string, stat:function([]):number}[]} spec.cell
1297 * @param {function({}):number} spec.calc - ({col1,col2,...})=>number
1298 * @returns {CrosTab}
1299 */
1300
1301
1302 figure(spec) {
1303 return crosTabFut(this, spec);
1304 }
1305
1306}
1307
1308var _fieldIndexes = new WeakSet();
1309
1310var _fieldIndexes2 = function _fieldIndexes2(fields) {
1311 const hi = fields === null || fields === void 0 ? void 0 : fields.length;
1312 if (!hi) return {
1313 indexes: [],
1314 banner: []
1315 };
1316 const [indexes, banner] = [Array(hi), Array(hi)];
1317
1318 for (let i = 0, x; i < hi; i++) {
1319 x = fields[i];
1320 [indexes[i], banner[i]] = Array.isArray(x) ? [this.coin(x[0]), x[1]] : [this.coin(x), x];
1321 }
1322
1323 return {
1324 indexes,
1325 banner
1326 };
1327};
1328
1329class TableJoin {
1330 /**
1331 *
1332 * @param {Table} table
1333 * @param {Table} another
1334 * @param {string[]|number[]} indexFields
1335 * @param {number} [joinType=-1] - union:0,left:1,right:2,intersect:-1
1336 * @param {*} [fillEmpty]
1337 * @returns {Table}
1338 */
1339 static join(table, another, indexFields, joinType = JoinT.intersect, fillEmpty = null) {
1340 const ysL = indexFields.map(x => table.coin(x)),
1341 ysR = indexFields.map(x => another.coin(x)),
1342 joinFn = joiner(joinType),
1343 matrix = joinFn({
1344 mx: table.matrix,
1345 ys: ysL
1346 }, {
1347 mx: another.matrix,
1348 ys: ysR
1349 }, fillEmpty),
1350 banner = Ar.select(table.banner, ysL).concat(Ar.splices(table.banner.slice(), ysL.slice().sort(Comparer.numberAscending)), Ar.splices(another.banner.slice(), ysL.slice().sort(Comparer.numberAscending)));
1351 return new Table(banner, matrix, `${table.title} ${another.title}`);
1352 }
1353
1354}
1355
1356// Create an object type Er
1357class Er extends Error {
1358 constructor(name, message) {
1359 super();
1360 this.name = name;
1361 this.message = message;
1362 }
1363
1364 static r({
1365 name,
1366 message
1367 }) {
1368 return new Er(name || 'Error', message || '');
1369 } // Make the exception convert to a pretty string when used as
1370 // a string (e.g. by the error console)
1371
1372
1373 toString() {
1374 return `${this.name}: "${this.message}"`;
1375 }
1376
1377}
1378
1379/**
1380 *
1381 * @param {string|string[]|Object<string|number,function(*[]):number>} cell
1382 * @private
1383 */
1384
1385const createCell = cell => {
1386 if (!cell) return {};
1387
1388 switch (Typ.infer(cell)) {
1389 case 'string':
1390 return Ob.of([cell, 'sum']);
1391
1392 case 'object':
1393 return Ob.mapValues(cell, x => x || 'sum');
1394
1395 case 'array':
1396 return cell.length ? cell.map(el => typeof el === 'string' ? Ob.of([el, 'sum']) : Ob.of([el.field || '', el.stat || 'sum'])) : {};
1397
1398 default:
1399 throw Er.r({
1400 message: 'Input is neither string nor array'
1401 });
1402 }
1403};
1404/***
1405 *
1406 * @property {string} side - side, a string
1407 * @property {string} banner - banner, a string
1408 * @property {Object<string|number,function(*):boolean>} [filter] - filter definition
1409 * @property {Object<string|number,function(*[]):number>} [cell] - cell definition
1410 * @property {function():number} [calc] - calc definition: function<field1,field2,...,number>
1411 */
1412
1413
1414class TableSpec {
1415 constructor(side, banner, cell, filter, calc) {
1416 var _cell;
1417
1418 if (typeof side !== 'string') throw Er.r({
1419 message: 'Side is not string'
1420 });
1421 if (typeof banner !== 'string') throw Er.r({
1422 message: 'Banner is not string'
1423 });
1424 this.side = side;
1425 this.banner = banner;
1426 this.cell = (_cell = cell, createCell(_cell));
1427 this.filter = filter || {};
1428 this.calc = calc;
1429 }
1430
1431 static from({
1432 side,
1433 banner,
1434 cell,
1435 filter,
1436 calc
1437 }) {
1438 return new TableSpec(side, banner, cell, filter, calc);
1439 }
1440
1441 get title() {
1442 return `${this.side} * ${this.banner} · ${this.fields}`;
1443 }
1444
1445 get fields() {
1446 return Object.keys(this.cell);
1447 }
1448
1449 get filterFields() {
1450 return Object.keys(this.filter);
1451 }
1452
1453 get toJson() {
1454 const {
1455 side,
1456 banner,
1457 cell,
1458 filter,
1459 calc
1460 } = this;
1461 return {
1462 side,
1463 banner,
1464 cell,
1465 filter,
1466 calc
1467 };
1468 }
1469
1470}
1471
1472export { CrosTab, JoinT, Table, TableJoin, TableSpec };