TypeScript实现向量与矩阵

2,629 阅读3分钟

前言

作为一个对线性代数一无所知的开发者,想快速对向量和矩阵进行一个了解和认识,那么本文就正好适合你。

本文将站在开发者的角度讲解向量和矩阵,并用TypeScript将其实现,欢迎各位感兴趣的开发者阅读本文。

向量

向量是线性代数研究的基本元素,将一组数放在一起其基本的表示方法就是向量,例如:一个数: 100,一组数:(25,78,101)。其中一组数就可以称为向量,示例中的这组数是一个三维向量。

下述公式,描述了一个向量。

mˉ=(257831)\bar{m} = \left( \begin{matrix} 25 \\ 78 \\ 31 \end{matrix} \right)

那么,一组数有什么用?

我们通过一个例子来说明,如下表所示:

序号语文数学英语
0708090

上述表格中,(0,70,80,90) 这组数分别描述了序号、语文、数学、英语,如果将这组数中数字的顺序调换顺序,那么其所表达的意思也就完全不同了,每个数字都代表着空间中的一个点,是一组有序的数字,因此他可以用来描述一个有序的东西。

标量:向量中的数字就称为标量。

向量的更多概念

行向量与列向量

  • 行向量:即横着排列的标量,如下所示

(3,4)\left ( 3,4 \right )

  • 列向量:即竖着排列的标量,如下所示

(5,2)T\left ( 5,2 \right )^T

维度

向量中标量的个数,就是这个向量的维度。

例如,向量(3,4)其维度就是2

实现向量

接下来我们根据上面所述,先来看看我们都需要为向量实现哪些方法。

  • 获取向量的维度
  • 向量的长度
  • 获取向量的特定元素
  • 输出向量

接下来,我们来一一实现这些方法。

  • 创建一个TS文件,命名为:Vector.ts,用于实现向量的所有方法
  • 声明向量类,在构造函数中声明我们需要传的参数,向量就是一组数,因此我们用数组来表示向量
export class Vector {
    constructor(private list: number[] = []) {}
}
  • 实现获取向量维度函数:getDimension
    getDimension(): number {
        return this.list.length;
    }
  • 实现向量的长度函数:length
    len = this.getDimension();
  • 实现获取向量的特定元素:getItem
    /**
     * 获取向量的特定元素
     * @param index 目标元素索引
     */
    getItem(index: number): number {
        return this.list[index];
    }
  • 实现输出向量函数
    toStr(): string {
        let str = ``;
        for (let i = 0; i < this.list.length; i++) {
            if (i !== this.list.length - 1) {
                str += `${this.list[i]},`;
            } else {
                str += this.list[i];
            }
        }
        return `Vector(${str})`;
    }

向量的基本运算

向量有两种基本运算:即向量加法和向量数量乘法

向量的加法

(5,2)T+(4,6)T=(5+4,2+6)T=(9,8)T\left ( 5,2 \right )^T + \left ( 4,6 \right )^T = \left ( 5+4,2+6 \right )^T = \left ( 9,8 \right )^T

如上所示,描述了两个向量相加,它的计算规则如下:

  • 相加的两个向量其维度必须相等
  • 把向量中的分量(即向量中的每个数)分别想加,最终构成的向量就是其相加后的结果。

向量的数量乘法

用一个向量和一个标量进行乘法运算,就称之为向量的数量乘法。

(5,2)T2=(52,22)T=(10,4)T\left ( 5,2 \right )^T * 2 = \left ( 5 * 2,2 * 2 \right )^T = \left ( 10,4 \right )^T

如上所示,描述了向量和标量相乘,它的计算规则如下:

  • 把向量中的分量与分别与标量相乘,最终构成的向量就是其相乘后的结果。

向量的点乘

两个向量相乘就称之为向量的点乘运算。

(5,2)T(2,6)T=52+26=10+12=22\left ( 5,2 \right )^T * \left ( 2,6 \right )^T = 5*2+2*6 = 10 + 12 = 22

如上所示,描述了向量与向量相乘,它的计算规则如下:

  • 相乘的两个向量,其维度必须相等
  • 把两个向量的分量分别相乘,将其结果相加,最终得到的标量就是其相乘后的结果

实现向量的运算

上面我们讲解了向量的两个基本运算,接下来我们就可以根据这两个基本运算推导出更多的结论,我们将要实现的函数如下。

  • 向量的加法运算
  • 向量的减法运算
  • 向量的乘法运算
  • 向量的除法运算
  • 向量取正
  • 向量取负
  • 向量的点乘

我们将上述函数一一实现

  • 实现加法运算:add
    /**
     * 向量的加法运算
     * @param another 需要进行相加的向量
     */
    add(another: Vector): Vector | string {
        // 向量相加时,要确保其维度与当前维度相等
        if (this.getDimension() === another.getDimension()) {
            const finalList: number[] = [];
            for (let i = 0; i < this.getDimension(); i++) {
                finalList.push(this.getItem(i) + another.getItem(i));
            }
            return new Vector(finalList);
        } else {
            return "维度不相等,无法进行加法运算";
        }
    }
  • 实现减法运算
    /**
     * 向量的减法运算
     * @param another
     */
    sub(another: Vector): Vector | string {
        // 与加法运算一样,维度必须相等
        if (this.getDimension() === another.getDimension()) {
            const finalList: number[] = [];
            for (let i = 0; i < this.getDimension(); i++) {
                finalList.push(this.getItem(i) - another.getItem(i));
            }
            return new Vector(finalList);
        } else {
            return "维度不相等,无法进行减法运算";
        }
    }
  • 实现数量乘法运算
    
    /**
     * 向量的数量乘法运算
     * @param K
     */
    mul(K: number): Vector {
        const finalList: number[] = [];
        // 向量的乘法运算规则:用向量的每一个维度去和这个数相乘
        for (let i = 0; i < this.getDimension(); i++) {
            finalList.push(this.getItem(i) * K);
        }

        return new Vector(finalList);
    }
  • 实现数量除法运算
    division(K: number): Vector {
        return this.mul(1 / K);
    }
  • 实现向量取正和向量取负
    // 向量取正,即将向量中分量变成正数
    pos(): Vector {
        return this.mul(1);
    }

    // 向量取负,即将向量中分量变成负数
    neg(): Vector {
        return this.mul(-1);
    }
  • 实现向量的点乘
    dotMul(another: Vector): number | void {
        if (another.getDimension() === this.getDimension()) {
            let final = 0;
            // 两个向量点乘的方法: 将每个向量中的元素互相进行乘法运算,将得到的结果相加
            for (let i = 0; i < this.getDimension(); i++) {
                final += this.getItem(i) * another.getItem(i);
            }
            return final;
        } else {
            console.log("两个向量点乘时其维度必须相等");
        }
    }

矩阵

矩阵就是对向量的扩展,将一组向量放在一起就可以构建成一个矩阵,我们可以从两个角度去看待一个矩阵:行向量和列向量。

A=(a11a12a13a14a21a22a23a24a31a32a33a34)A = \left( \begin{matrix} a_{11} & a_{12} & a_{13} & a_{14}\\ a_{21} & a_{22} & a_{23} & a_{24} \\ a_{31} & a_{32} & a_{33} & a_{34} \end{matrix} \right)

如上所示,描述了一个3*4的矩阵,用数学公式表示为:A(m*n)),其中m表示其行数,n表示其列数。

在上述矩阵中,a11表示其在矩阵A的第1行第1列,a23表示其在矩阵A的第2行的第3列,因此我们通常会用aij来描述矩阵中的某个元素,i表示行,j表示列。

如果我们通过行向量的角度来看待这个矩阵的话,它就由3个向量组成。 如果我们通过列向量的角度来看待这个矩阵的话,它就由4个向量组成。

实现矩阵

我们来看看实现一个矩阵都要实现哪些方法: 根据上述矩阵的描述,我们可以使用二维数组来描述矩阵。

  • 获取矩阵的形状,返回这个矩阵由几行几列组成

    • 行数就是二维数组的长度
    • 列数就是二维数组的中0号数组的长度
  • 获取矩阵的行数,获取矩阵的列数。返回矩阵形状中求出的行数和列数即可

  • 获取矩阵的大小,用矩阵的行数 * 矩阵的列数

  • 矩阵的长度,返回矩阵的行数

  • 获取矩阵的行向量,返回二维数组的指定位置的数组

  • 获取矩阵的列向量

  • 获取矩阵的中的特定元素

接下来,我们将上述方法一一实现。

  • 创建Matrix.ts文件,用于实现矩阵。
  • 创建Matrix类,声明构造函数需要传的参数
export class Matrix {
    constructor(private twoDimArray: number[][]) {}
}    
  • 获取矩阵的形状:shape
    /**
     * 矩阵形状
     * @returns [x,y] x行,y列
     */
    shape(): number[] {
        // 矩阵的长度就是矩阵的行数,矩阵中每个向量的维度就是其列数都相等,所以取其第0号元素的作为列数
        return [this.twoDimArray.length, this.twoDimArray[0].length];
    }
  • 获取矩阵行数getRowNum和列数getColNum
    // 获取矩阵行数
    getRowNum(): number {
        return this.shape()[0];
    }

    // 获取矩阵列数
    getColNum(): number {
        return this.shape()[1];
    }
  • 获取矩阵大小和长度:size
    size(): number {
        const shape = this.shape();
        return shape[0] * shape[1];
    }
    // 矩阵的长度,即:获取矩阵行数
    len = this.getRowNum();
  • 获取矩阵的行向量:rowVector
    rowVector(index: number): Vector {
        return new Vector(this.twoDimArray[index]);
    }
  • 获取矩阵的列向量:colVector
    colVector(index: number): Vector {
        // 存放指定列的元素
        const finalList: number[] = [];
        for (let i = 0; i < this.getRowNum(); i++) {
            // 取出矩阵的每一行
            const row = this.twoDimArray[i];
            // 取出每一行中的指定列,将其存起来
            finalList.push(row[index]);
        }
        // 返回向量
        return new Vector(finalList);
    }
  • 获取矩阵中的元素:getItem
    getItem(position: number[]): number {
        return this.twoDimArray[position[0]][position[1]];
    }

矩阵的基本运算

矩阵的运算可分为:矩阵与矩阵相加、矩阵与标量相乘、矩阵与向量相乘、矩阵与矩阵相乘。

矩阵的加法运算

矩阵与矩阵相加就称为矩阵的加法运算。 T+P=(12579101326217122)+(12345678111347)=(137101315199141330529)T+P = \left( \begin{matrix} 12 & 5 & 7 & 9 \\ 10 & 13 & 2 & 6 \\ 2 & 17 & 1 & 22 \end{matrix} \right) + \left( \begin{matrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 11 & 13 & 4 & 7 \end{matrix} \right) = \left( \begin{matrix} 13 & 7 & 10 & 13 \\ 15 & 19 & 9 & 14 \\ 13 & 30 & 5 & 29 \end{matrix} \right)

上述公式描述了矩阵加法的运算过程,其运算方法如下:

  • 两个矩阵相加其大小必须相等
  • 取出两个矩阵中的元素,将其相加构建成新的矩阵就是矩阵相加的结果。

矩阵数量乘法

矩阵与标量之间的乘法运算就称为矩阵数量乘法。

Tk=(12579101326217122)2=(241014182026412434244)T*k = \left( \begin{matrix} 12 & 5 & 7 & 9 \\ 10 & 13 & 2 & 6 \\ 2 & 17 & 1 & 22 \end{matrix} \right) * 2 = \left( \begin{matrix} 24 & 10 & 14 & 18 \\ 20 & 26 & 4 & 12 \\ 4 & 34 & 2 & 44 \end{matrix} \right)

上述公式描述了矩阵与标量相乘的运算过程,其运算方法如下:

  • 将矩阵中的每个元素和标量相乘,其结果构建成一个新的矩阵就是矩阵数量乘法的结果。

矩阵与向量相乘

Tmˉ=(133557)(12)=(11+32=1+6=731+52=3+10=1351+72=5+14=19)T*\bar{m} = \left( \begin{matrix} 1 & 3 \\ 3 & 5 \\ 5 & 7 \end{matrix} \right) * \left( \begin{matrix} 1 \\ 2 \end{matrix} \right) = \left( \begin{matrix} 1*1+3*2=1+6=7 \\ 3*1+5*2=3+10=13 \\ 5*1+7*2=5+14=19 \end{matrix} \right) 上述公式描述了矩阵与向量相乘的运算过程,其运算方法如下:

  • 矩阵与向量相乘时,矩阵的列数必须与向量的长度相等
  • 获取矩阵的行向量,将矩阵的每个行向量与向量进行点乘运算

矩阵与矩阵相乘

TP=(1.5002)(045003)=(1.50+00=01.54+00=61.55+03=7.500+20=004+20=005+23=6)T*P = \left( \begin{matrix} 1.5 & 0 \\ 0 & 2 \end{matrix} \right) * \left( \begin{matrix} 0 & 4 & 5 \\ 0 & 0 & 3 \end{matrix} \right) = \left( \begin{matrix} 1.5*0+0*0=0 & 1.5*4+0*0=6 & 1.5*5+0*3=7.5 \\ 0*0+2*0=0 & 0*4+2*0=0 & 0*5+2*3=6 \end{matrix} \right)

上述公式描述了矩阵与矩阵相乘的运算过程,其运算方法如下:

  • 矩阵与矩阵相乘时,第一个矩阵的列数必须等于第二个矩阵的行数
  • 将第一个矩阵拆分为一个个的行向量,将第二个矩阵拆分为一个个的列向量
  • 用拆分出来的行向量,与拆分出来的每个列向量进行点乘运算,将返回的向量放在一起,构建成出的新的矩阵就是其相乘得到的结果。

实现矩阵的基本运算

接下来,我们用代码将上述运算一一实现。

  • 实现矩阵的加法运算add和减法运算sub
    /**
     * 加法运算
     * @param another 另一个矩阵
     * @return Matrix 新的矩阵
     */
    add(another: Matrix): Matrix | void {
        // 两个矩阵相加,其大小必须相等
        if (this.size() === another.size()) {
            const finalList: number[][] = [];
            // 将矩阵的每个元素相加,构成新的矩阵
            for (let i = 0; i < this.getRowNum(); i++) {
                const row: number[] = [];
                for (let j = 0; j < this.getColNum(); j++) {
                    // 相加每个元素
                    row.push(this.getItem([i, j]) + another.getItem([i, j]));
                }
                // 构建新的矩阵
                finalList.push(row);
            }
            return new Matrix(finalList);
        } else {
            console.log("矩阵相加,其大小必须相等");
        }
    }

    /**
     * 减法运算
     * @param another 另一个矩阵
     * @return Matrix 新的矩阵
     */
    sub(another: Matrix): Matrix | void {
        // 两个矩阵相加,其大小必须相等
        if (this.size() === another.size()) {
            const finalList: number[][] = [];
            // 将矩阵的每个元素相加,构成新的矩阵
            for (let i = 0; i < this.getRowNum(); i++) {
                const row: number[] = [];
                for (let j = 0; j < this.getColNum(); j++) {
                    // 相加每个元素
                    row.push(this.getItem([i, j]) - another.getItem([i, j]));
                }
                // 构建新的矩阵
                finalList.push(row);
            }
            return new Matrix(finalList);
        } else {
            console.log("矩阵相减,其大小必须相等");
        }
    }
  • 实现矩阵数量的乘法运算mul和除法运算division
    /**
     * 矩阵数量乘法
     * @param K 目标值
     * @return Matrix 新的矩阵
     */
    mul(K: number): Matrix {
        const finalList: number[][] = [];
        // 运算规则: 用矩阵中每个向量的元素去乘以目标数量,返回新的矩阵
        for (let i = 0; i < this.getRowNum(); i++) {
            const row: number[] = [];
            for (let j = 0; j < this.getColNum(); j++) {
                row.push(this.getItem([i, j]) * K);
            }
            finalList.push(row);
        }
        // 构建新的矩阵并返回
        return new Matrix(finalList);
    }

    /**
     * 矩阵数量除法
     * @param K 目标值
     * @return Matrix 新的矩阵
     */
    division(K: number): Matrix {
        return this.mul(1 / K);
    }
  • 实现矩阵与向量相乘:mulVector
    /**
     * 矩阵与向量相乘
     * @param vector 进行乘法运算的的向量
     * @return Vector 生成的新向量
     */
    mulVector(vector: Vector): Vector | void {
        // 矩阵与向量相乘时其列数必须等于向量的长度
        if (vector.len === this.getColNum()) {
            // 结果数组
            const finalList: number[] = [];
            // 计算规则:
            //    1. 用矩阵的每一项的与向量的每一项做乘法运算
            //    2. 将得到的结果累加
            //    3. 将累加的结果放进结果数组中
            //    4. 根据结果数组构建新的向量
            for (let i = 0; i < this.getRowNum(); i++) {
                // 当前矩阵的行向量与向量进行点运算
                finalList.push(<number>this.rowVector(i).dotMul(vector));
            }
            // 遍历结束,根据结果生成向量,并将其返回
            return new Vector(finalList);
        } else {
            console.log("矩阵与向量相乘时,矩阵的列数必须与向量的长度相等");
        }
    }
  • 实现矩阵与矩阵相乘
    /**
     * 矩阵与矩阵相乘
     * @param matrix
     */
    mulMatrix(matrix: Matrix): Matrix | void {
        // 矩阵A的列数必须和矩阵P的行数相等
        if (this.getColNum() === matrix.getRowNum()) {
            // 存放最终结果的二维数组
            const finalList = [];
            // 拆分两个矩阵,将其拆分成向量与向量之间的点乘
            for (let i = 0; i < this.getRowNum(); i++) {
                // 存放结果行向量的数组
                const resultList: number[] = [];
                // 获取矩阵A的行向量
                const rowVector = this.rowVector(i);
                for (let j = 0; j < matrix.getColNum(); j++) {
                    // 获取矩阵P的列向量
                    const colVector = matrix.colVector(j);
                    // 将行向量与列向量进行点乘,将结果放进结果行向量数组中
                    resultList.push(<number>rowVector.dotMul(colVector));
                }
                // 将构建好的结果行向量放进最终结果的二维数组
                finalList.push(resultList);
            }
            // 根据最终结果的二维数组构建矩阵
            return new Matrix(finalList);
        } else {
            console.log("矩阵与矩阵相乘,其中一个矩阵的列数必须与另一个矩阵的行数相等");
        }
    }

代码地址

本文用到的代码完整地址以及测试代码请移步GitHub仓库:

向量:Vector.ts & vectorTest.js

矩阵:Matrix.ts & MatrixTest.ts

写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于掘金,未经许可禁止转载💌