Skip to content

Commit cfe50db

Browse files
authored
feat: add Symmetric and Distance Matrix (#178)
* add iterator, entries and values on AbstractMatrix * isDistance on AbstractMatrix * static copy on AbstractMatrix * add missing addColumn / addRow signature on Matrix * Symmetric (and Distance) Matrix got upperRight iterators * toCompact / fromCompact 1D flat upper corner representation * complete unit tests
1 parent 72bdf3a commit cfe50db

15 files changed

+1323
-23
lines changed

matrix.d.ts

Lines changed: 267 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
type MaybeMatrix = AbstractMatrix | ArrayLike<ArrayLike<number>>;
22
type ScalarOrMatrix = number | MaybeMatrix;
33
type MatrixDimension = 'row' | 'column';
4+
type MaskValue = 0 | 1 | number | boolean;
5+
/**
6+
* allow use of numbers (only 0 is considered false) and booleans
7+
*/
8+
type Mask = MaskValue[];
49

510
export interface IRandomOptions {
611
/**
@@ -165,17 +170,24 @@ export abstract class AbstractMatrix {
165170
* This is equivalent to calling the Matrix constructor.
166171
* @param rows - Number of rows.
167172
* @param columns - Number of columns.
173+
* @template _M is private. Don't override it.
168174
* @returns The new matrix.
169175
*/
170-
static zeros(rows: number, columns: number): Matrix;
176+
static zeros<_M extends AbstractMatrix = Matrix>(
177+
rows: number,
178+
columns: number,
179+
): _M;
171180

172181
/**
173182
* Creates a matrix with the given dimensions. Values will be set to one.
174183
* @param rows - Number of rows.
175184
* @param columns - Number of columns.
176185
* @returns The new matrix.
177186
*/
178-
static ones(rows: number, columns: number): Matrix;
187+
static ones<M extends AbstractMatrix = Matrix>(
188+
rows: number,
189+
columns: number,
190+
): M;
179191

180192
/**
181193
* Creates a matrix with the given dimensions. Values will be randomly set.
@@ -195,6 +207,7 @@ export abstract class AbstractMatrix {
195207
* Creates a matrix with the given dimensions. Values will be random integers.
196208
* @param rows - Number of rows.
197209
* @param columns - Number of columns.
210+
* @param options
198211
* @returns - The new matrix.
199212
*/
200213
static randInt(
@@ -313,6 +326,11 @@ export abstract class AbstractMatrix {
313326
*/
314327
isSquare(): boolean;
315328

329+
/**
330+
* Returns whether the matrix is symmetric and diagonal values are equals to 0
331+
*/
332+
isDistance(): boolean;
333+
316334
/**
317335
* Returns whether the number of rows or columns (or both) is zero.
318336
*/
@@ -728,7 +746,9 @@ export abstract class AbstractMatrix {
728746
/**
729747
* Creates an exact and independent copy of the matrix.
730748
*/
731-
clone(): Matrix;
749+
clone(): this;
750+
751+
static copy<M extends AbstractMatrix>(from: AbstractMatrix, to: M): M;
732752

733753
/**
734754
* Returns the sum of all elements of the matrix.
@@ -824,6 +844,34 @@ export abstract class AbstractMatrix {
824844

825845
toString(options?: IToStringOptions): string;
826846

847+
// iterators methods
848+
849+
/**
850+
* iterator from left to right, from top to bottom
851+
* yield [row, column, value]
852+
*/
853+
[Symbol.iterator](): Generator<
854+
[row: number, column: number, value: number],
855+
void,
856+
never
857+
>;
858+
859+
/**
860+
* iterator from left to right, from top to bottom
861+
* yield [row, column, value]
862+
*/
863+
entries(): Generator<
864+
[row: number, column: number, value: number],
865+
void,
866+
never
867+
>;
868+
869+
/**
870+
* iterator from left to right, from top to bottom
871+
* yield value
872+
*/
873+
values(): Generator<number, void, never>;
874+
827875
// From here we document methods dynamically generated from operators
828876

829877
// Mathematical operators
@@ -960,17 +1008,233 @@ export class Matrix extends AbstractMatrix {
9601008
* @param array - Column to add.
9611009
*/
9621010
addColumn(index: number, array: ArrayLike<number> | AbstractMatrix): this;
1011+
addColumn(array: ArrayLike<number> | AbstractMatrix): this;
9631012

9641013
/**
9651014
* Adds a new row to the matrix (in place).
9661015
* @param index - Row index. Default: `this.rows`.
9671016
* @param array - Row to add.
9681017
*/
9691018
addRow(index: number, array: ArrayLike<number> | AbstractMatrix): this;
1019+
addRow(array: ArrayLike<number> | AbstractMatrix): this;
9701020
}
9711021

9721022
export default Matrix;
9731023

1024+
export class SymmetricMatrix extends AbstractMatrix {
1025+
/**
1026+
* alias for `rows` or `columns` (square matrix so equals)
1027+
*/
1028+
readonly diagonalSize: number;
1029+
1030+
/**
1031+
* @throws TypeError if otherMatrix is not symmetric
1032+
* @param otherMatrix
1033+
*/
1034+
constructor(otherMatrix: AbstractMatrix);
1035+
constructor(diagonalSize: number);
1036+
/**
1037+
* @throws TypeError if data are not symmetric
1038+
* @param data
1039+
*/
1040+
constructor(data: ArrayLike<ArrayLike<number>>);
1041+
1042+
get(rowIndex: number, columnIndex: number): number;
1043+
set(rowIndex: number, columnIndex: number, value: number): this;
1044+
1045+
/**
1046+
* Creates a symmetric matrix with the given dimensions. Values will be set to zero.
1047+
* This is equivalent to calling the Matrix constructor.
1048+
*
1049+
* @param diagonalSize - Number of rows or columns (square).
1050+
* @template _M is private, do not override it.
1051+
* @returns The new symmetric matrix.
1052+
*/
1053+
static zeros<_M extends AbstractMatrix = SymmetricMatrix>(
1054+
diagonalSize: number,
1055+
): _M;
1056+
/**
1057+
* Creates a symmetric matrix with the given dimensions. Values will be set to one.
1058+
* @param diagonalSize - Number of rows or columns (square).
1059+
* @template _M is private, do not override it.
1060+
* @returns The new symmetric matrix.
1061+
*/
1062+
static ones<_M extends AbstractMatrix = SymmetricMatrix>(
1063+
diagonalSize: number,
1064+
): _M;
1065+
1066+
static isSymmetricMatrix(value: unknown): value is SymmetricMatrix;
1067+
1068+
/**
1069+
* copy to a new matrix
1070+
*/
1071+
toMatrix(): Matrix;
1072+
1073+
/**
1074+
* Symmetric remove row / column
1075+
* @param index
1076+
*/
1077+
removeCross(index: number): this;
1078+
1079+
/**
1080+
* Symmetric add row / column
1081+
* @param index
1082+
* @param array
1083+
*/
1084+
addCross(index: number, array: ArrayLike<number> | AbstractMatrix): this;
1085+
addCross(array: ArrayLike<number> | AbstractMatrix): this;
1086+
1087+
/**
1088+
* remove sides (rows / columns) with falsy value from mask.
1089+
*
1090+
* @example
1091+
*
1092+
* ```js
1093+
* const matrix = new SymmetricMatrix([
1094+
* [0,1,2,3],
1095+
* [1,0,4,5],
1096+
* [2,4,0,6],
1097+
* [3,5,6,0],
1098+
* ]);
1099+
* matrix.applyMask([1,0,0,1]);
1100+
* assert.deepEqual(matrix.toCompact(), new SymmetricMatrix([
1101+
* [0,3],
1102+
* [3,0],
1103+
* ]).toCompact());
1104+
* ```
1105+
*
1106+
* @throws RangeError if mask length is different of matrix sideSize
1107+
*
1108+
* @param mask
1109+
*/
1110+
applyMask(mask: Mask): this;
1111+
1112+
/**
1113+
* Compact format upper-right corner of matrix
1114+
* iterate from left to right, from top to bottom.
1115+
*
1116+
* ```
1117+
* full view | usefull data
1118+
* A B C D | A B C D
1119+
* A 1 2 3 4 | A 1 2 3 4
1120+
* B 2 5 6 7 | B · 5 6 7
1121+
* C 3 6 8 9 | C · · 8 9
1122+
* D 4 7 9 10 | D · · · 10
1123+
* ```
1124+
*
1125+
* will return compact 1D array `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`
1126+
*
1127+
* length is S(i=0, n=sideSize) => 10 for a 4 sideSized matrix
1128+
*/
1129+
toCompact(): number[];
1130+
1131+
/**
1132+
* @throws TypeError if `compact` is not the compact form of a Symmetric Matrix
1133+
* `(Math.sqrt(8 * compactLength + 1) - 1) / 2` must be an integer
1134+
*
1135+
* @param compact
1136+
*/
1137+
static fromCompact(compact: number[]): SymmetricMatrix;
1138+
1139+
clone(): this;
1140+
1141+
/**
1142+
* half iterator upper-right-corner from left to right, from top to bottom
1143+
* yield [row, column, value]
1144+
*/
1145+
upperRightEntries(): Generator<
1146+
[row: number, column: number, value: number],
1147+
void,
1148+
never
1149+
>;
1150+
1151+
/**
1152+
* half iterator upper-right-corner from left to right, from top to bottom
1153+
* yield value
1154+
*/
1155+
upperRightValues(): Generator<number, void, never>;
1156+
}
1157+
1158+
export class DistanceMatrix extends SymmetricMatrix {
1159+
/**
1160+
* Creates a distance matrix with the given dimensions. Values will be set to zero.
1161+
* This is equivalent to calling the Matrix constructor.
1162+
* @param sidesSize - Number of rows or columns (square).
1163+
* @template _M is private, do not specify it
1164+
* @returns The new symmetric matrix.
1165+
*/
1166+
static zeros<_M extends AbstractMatrix = DistanceMatrix>(
1167+
sidesSize: number,
1168+
): _M;
1169+
1170+
/**
1171+
* Creates a symmetric matrix with the given dimensions. Values will be set to one.
1172+
* @param sidesSize - Number of rows or columns (square).
1173+
* @template _M is private, do not specify it
1174+
* @returns The new symmetric matrix.
1175+
*/
1176+
static ones<_M extends AbstractMatrix = DistanceMatrix>(
1177+
sidesSize: number,
1178+
): _M;
1179+
1180+
static isDistanceMatrix(value: unknown): value is DistanceMatrix;
1181+
1182+
constructor(sidesSize: number);
1183+
/**
1184+
* @throws TypeError if data are not symmetric and diagonal is not 0
1185+
* @param data
1186+
*/
1187+
constructor(data: ArrayLike<ArrayLike<number>>);
1188+
/**
1189+
* @throws TypeError if otherMatrix is not symmetric and diagonal is not 0
1190+
* @param otherMatrix
1191+
*/
1192+
constructor(otherMatrix: AbstractMatrix);
1193+
1194+
/**
1195+
* because it's a distance matrix, if rowIndex === columnIndex,
1196+
* value will be set to 0
1197+
*
1198+
* @param rowIndex
1199+
* @param columnIndex
1200+
* @param value
1201+
*/
1202+
set(rowIndex: number, columnIndex: number, value: number): this;
1203+
1204+
toSymmetricMatrix(): SymmetricMatrix;
1205+
1206+
/**
1207+
* Compact format upper-right corner of matrix
1208+
* no diagonal (because only store zeros)
1209+
* iterable from left to right, from top to bottom.
1210+
*
1211+
* ```
1212+
* A B C D
1213+
* A 0 1 2 3
1214+
* B 1 0 4 5
1215+
* C 2 4 0 6
1216+
* D 3 5 6 0
1217+
* ```
1218+
*
1219+
* will return compact 1D array `[1, 2, 3, 4, 5, 6]`
1220+
*
1221+
* length is S(i=0, n=sideSize-1) => 6 for a 4 side sized matrix
1222+
*
1223+
* @returns {number[]}
1224+
*/
1225+
toCompact(): number[];
1226+
1227+
/**
1228+
* @throws TypeError if `compact` is not the compact form of a Distance Matrix
1229+
* `(Math.sqrt(8 * compactSize + 1) + 1) / 2` must be an integer
1230+
*
1231+
* @param compact
1232+
*/
1233+
static fromCompact(compact: number[]): DistanceMatrix;
1234+
1235+
clone(): this;
1236+
}
1237+
9741238
export class MatrixColumnView extends AbstractMatrix {
9751239
constructor(matrix: AbstractMatrix, column: number);
9761240
set(rowIndex: number, columnIndex: number, value: number): this;

0 commit comments

Comments
 (0)