Skip to content

Instantly share code, notes, and snippets.

@tomowarkar
Last active April 3, 2021 19:16
Show Gist options
  • Save tomowarkar/a43ae0986779ba38fa92c1be84b49bd6 to your computer and use it in GitHub Desktop.
Save tomowarkar/a43ae0986779ba38fa92c1be84b49bd6 to your computer and use it in GitHub Desktop.

Typescriptで書いた行列演算ライブラリ

  • 転置
  • 逆行列
  • 3次元座標変換

以上の最低限を実装

Usage

// Zero matrix
new Matrix(3, 2)  // Matrix { height: 3, width: 2, data: [ 0, 0, 0, 0, 0, 0 ] }

// Eye matrix
// note: Matrix.map returns new Matrix instance
new Matrix(3, 3).map((_, i, j) => (i === j ? 1 : 0))  // Matrix { height: 3, width: 3, data: [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ] }

let m = new Matrix(3, 2).init([
  1, 2, 
  3, 4, 
  5, 6]
)  // Matrix { height: 3, width: 2, data: [ 1, 2, 3, 4, 5, 6 ] }

// get value
m.at(0, 1)  // 2
m.at(-1, -1)  // 6

// update value
m.set(0, 1, 10)  // Matrix { height: 3, width: 2, data: [ 1, 10, 3, 4, 5, 6 ] }
m.at(0, 1)  // 10

m.dropRow(1)  // Matrix { height: 2, width: 2, data: [ 1, 10, 5, 6 ] }
m.dropCol(0)  // Matrix { height: 3, width: 1, data: [ 10, 4, 6 ] }

// Transpose
m.transpose() // Matrix { height: 2, width: 3, data: [ 1, 3, 5, 10, 4, 6 ] }

// Dot matrix
let m1 = new Matrix(3, 2).init([
  2, 3, 
  1, 4, 
  2, 1
]);

let m2 = new Matrix(2, 3).init([3, 1, 2, 2, 4, 2]);
let want1 = new Matrix(3, 3).init([12, 14, 10, 11, 17, 10, 8, 6, 6]);
let want2 = new Matrix(2, 2).init([11, 15, 12, 24]);

m1.dot(m2)  // Matrix { height: 3, width: 3, data: [ 12, 14, 10, 11, 17,  10,  8,  6,  6 ] }
m1.dot(m2).__eq(want1)  // true
m2.dot(m1).__eq(want2)  // true

// Square matrix
let mat3x3 = new SquareMatrix(3).init([
  1, 0, 2, 
  -1, 5, 0, 
  0, 3, -9
])
let want3 = new SquareMatrix(3).init([
  0.8823529411764706,  -0.11764705882352941,  0.19607843137254902,
  0.17647058823529413,  0.17647058823529413,  0.0392156862745098,
  0.058823529411764705,  0.058823529411764705,  -0.09803921568627451,
])

mat3x3.inv().__eq(want3)  // true
//
class Matrix {
height: number;
width: number;
data: number[];
constructor(height: number, width: number) {
this.height = height;
this.width = width;
this.data = new Array(height * width).fill(0);
}
init(data: number[]) {
data = data.filter(Number.isFinite);
if (data.length !== this.height * this.width) {
throw new Error("ValueError");
}
this.data = data;
return this;
}
__eq(other: Matrix) {
return (
this.height === other.height &&
this.width === other.width &&
this.data.toString() === other.data.toString()
);
}
isValid() {
let len = this.data.length;
return (
this.width * this.height === len &&
this.data.filter(Number.isFinite).length === len
);
}
_map(fn: (value: number, row: number, col: number) => number | undefined) {
return <Array<number>>this.data
.map((e, i) => {
let r = ~~(i / this.width);
let c = i % this.width;
return fn(e, r, c);
})
.filter(Number.isFinite);
}
map(fn: (value: number, row: number, col: number) => number) {
let m = this.copy();
m.data = this._map(fn);
if (m.isValid()) {
return m;
}
throw new Error("Broken data");
}
at(row: number, col: number) {
return this.data[row * this.width + col];
}
set(row: number, col: number, value: number) {
this.data[row * this.width + col] = value;
return this;
}
copy() {
return new Matrix(this.height, this.width).init(this.data);
}
dropRow(r: number) {
r = ((r % this.height) + this.height) % this.height;
let data = this._map((e, i, _) => (r === i ? undefined : e));
return new Matrix(this.height - 1, this.width).init(data);
}
dropCol(c: number) {
c = ((c % this.width) + this.width) % this.width;
let data = this._map((e, _, j) => (c === j ? undefined : e));
return new Matrix(this.height, this.width - 1).init(data);
}
transpose() {
let m = this.copy();
[m.width, m.height] = [m.height, m.width];
return m.map((_, i, j) => this.at(j, i));
}
dot(other: Matrix) {
if (this.height !== other.width || other.height !== this.width) {
throw new Error("matrix size error");
}
let m = new Matrix(this.height, other.width);
for (let i = 0; i < this.height; i++) {
for (let k = 0; k < this.width; k++) {
for (let j = 0; j < other.width; j++) {
m.set(i, j, m.at(i, j) + this.at(i, k) * other.at(k, j));
}
}
}
return m;
}
}
class SquareMatrix extends Matrix {
constructor(n: number) {
super(n, n);
}
get dim() {
return this.height;
}
copy() {
return new SquareMatrix(this.dim).init(this.data);
}
del(r: number, c: number) {
let data = this._map((e, i, j) => (r === i || c === j ? undefined : e));
return new SquareMatrix(this.dim - 1).init(data);
}
map(fn: (value: number, row: number, col: number) => number) {
let m = this.copy();
m.data = this._map(fn);
if (m.isValid()) {
return m;
}
throw new Error("Broken data");
}
det(): number {
if (this.dim === 1) {
return this.at(0, 0);
}
let m = this._map((e, i, j) => {
if (i !== 0) {
return 0;
}
let t = this.del(i, j);
e = (i + j) % 2 !== 0 ? -e : e;
return e * t.det();
});
return m.reduce((s, e) => s + e);
}
inv() {
let d = this.det();
return this.map((_, i, j) => {
return (this.del(i, j).det() * ((i + j) % 2 === 0 ? 1 : -1)) / d;
}).transpose();
}
transpose() {
let m = this.copy();
[m.width, m.height] = [m.height, m.width];
return m.map((_, i, j) => this.at(j, i));
}
}
class Mat4x4 extends SquareMatrix {
constructor() {
super(4);
// prettier-ignore
this.init([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
rotateX(angle: number) {
let cos = Math.cos(angle);
let sin = Math.sin(angle);
// prettier-ignore
return this.dot(
new Matrix(4, 4).init([
1, 0, 0, 0,
0, cos, sin, 0,
0, -sin, cos, 0,
0, 0, 0, 1
])
);
}
rotateY(angle: number) {
let cos = Math.cos(angle);
let sin = Math.sin(angle);
// prettier-ignore
return this.dot(
new Matrix(4, 4).init([
cos, 0, -sin, 0,
0, 1, 0, 0,
sin, 0, cos, 0,
0, 0, 0, 1
])
);
}
rotateZ(angle: number) {
let cos = Math.cos(angle);
let sin = Math.sin(angle);
// prettier-ignore
return this.dot(
new Matrix(4, 4).init([
cos, sin, 0, 0,
-sin, cos, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
);
}
scale(a: number, b: number, c: number) {
// prettier-ignore
return this.dot(
new Matrix(4, 4).init([
a, 0, 0, 0,
0, b, 0, 0,
0, 0, c, 0,
0, 0, 0, 1
])
);
}
move(a: number, b: number, c: number) {
// prettier-ignore
return this.dot(
new Matrix(4, 4).init([
1, 0, 0, a,
0, 1, 0, b,
0, 0, 1, c,
0, 0, 0, 1
])
);
}
calc(x: number, y: number, z: number) {
return this.dot(new Matrix(4, 1).init([x, y, z, 1])).data.slice(0, 3);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment