[译] JavaScript 线性代数:向量

本文是“JavaScript 线性代数”教程的一部分。javascript

向量是用于精确表示空间中方向的方法。向量由一系列数值构成,每维数值都是向量的一个份量。在下图中,你能够看到一个由两个份量组成的、在 2 维空间内的向量。在 3 维空间内,向量会由 3 个份量组成。前端

the vector in 2D space

咱们能够为 2 维空间的向量建立一个 Vector2D 类,而后为 3 维空间的向量建立一个 Vector3D 类。可是这么作有一个问题:向量并不只用于表示物理空间中的方向。好比,咱们可能须要将颜色(RGBA)表示为向量,那么它会有 4 个份量:红色、绿色、蓝色和 alpha 通道。或者,咱们要用向量来表示有不一样占比的 n 种选择(好比表示 5 匹马赛马,每匹马赢得比赛的几率的向量)。所以,咱们会建立一个不指定维度的类,并像这样使用它:java

class Vector {
  constructor(...components) {
    this.components = components
  }
}

const direction2d = new Vector(1, 2)
const direction3d = new Vector(1, 2, 3)
const color = new Vector(0.5, 0.4, 0.7, 0.15)
const probabilities = new Vector(0.1, 0.3, 0.15, 0.25, 0.2)
复制代码

向量运算

考虑有两个向量的状况,能够对它们定义如下运算:android

basic vector operations

其中,α ∈ R 为任意常数。ios

咱们对除了叉积以外的运算进行了可视化,你能够在此处找到相关示例。此 GitHub 仓库里有用来建立这些可视化示例的 React 项目和相关的库。若是你想知道如何使用 React 和 SVG 来制做这些二维可视化示例,请参考本文git

加法与减法

与数值运算相似,你能够对向量进行加法与减法运算。对向量进行算术运算时,能够直接对向量各自的份量进行数值运算获得结果:github

vectors addition

vectors subtraction

加法函数接收另外一个向量做为参数,并将对应的向量份量相加,返回得出的新向量。减法函数与之相似,不过会将加法换成减法:后端

class Vector {
  constructor(...components) {
    this.components = components
  }

  add({ components }) {
    return new Vector(
      ...components.map((component, index) => this.components[index] + component)
    )
  }
  subtract({ components }) {
    return new Vector(
      ...components.map((component, index) => this.components[index] - component)
    )
  }
}

const one = new Vector(2, 3)
const other = new Vector(2, 1)
console.log(one.add(other))
// Vector { components: [ 4, 4 ] }
console.log(one.subtract(other))
// Vector { components: [ 0, 2 ] }
复制代码

缩放

咱们能够对一个向量进行缩放,缩放比例可为任意数值 α ∈ R。缩放时,对全部向量份量都乘以缩放因子 α。当 α > 1 时,向量会变得更长;当 0 ≤ α < 1 时,向量会变得更短。若是 α 是负数,缩放后的向量将会指向原向量的反方向。函数

scaling vector

scaleBy 方法中,咱们对全部的向量份量都乘上传入参数的数值,获得新的向量并返回:post

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...

  scaleBy(number) {
    return new Vector(
      ...this.components.map(component => component * number)
    )
  }
}

const vector = new Vector(1, 2)
console.log(vector.scaleBy(2))
// Vector { components: [ 2, 4 ] }
console.log(vector.scaleBy(0.5))
// Vector { components: [ 0.5, 1 ] }
console.log(vector.scaleBy(-1))
// Vector { components: [ -1, -2 ] }
复制代码

长度

向量长度可由勾股定理导出:

vectors length

因为在 JavaScript 内置的 Math 对象中有现成的函数,所以计算长度的方法很是简单:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  length() {
    return Math.hypot(...this.components)
  }
}

const vector = new Vector(2, 3)
console.log(vector.length())
// 3.6055512754639896
复制代码

点积

点积能够计算出两个向量的类似程度。点积方法接收两个向量做为输入,并输出一个数值。两个向量的点积等于它们各自对应份量的乘积之和。

dot product

dotProduct 方法中,接收另外一个向量做为参数,经过 reduce 方法来计算对应份量的乘积之和:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  dotProduct({ components }) {
    return components.reduce((acc, component, index) => acc + component * this.components[index], 0)
  }
}

const one = new Vector(1, 4)
const other = new Vector(2, 2)
console.log(one.dotProduct(other))
// 10
复制代码

在咱们观察几个向量间的方向关系前,须要先实现一种将向量长度归一化为 1 的方法。这种归一化后的向量在许多情景中都会用到。好比说当咱们须要在空间中指定一个方向时,就须要用一个归一化后的向量来表示这个方向。

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  normalize() {
    return this.scaleBy(1 / this.length())
  }
}

const vector = new Vector(2, 4)
const normalized = vector.normalize()
console.log(normalized)
// Vector { components: [ 0.4472135954999579, 0.8944271909999159 ] }
console.log(normalized.length())
// 1
复制代码

using dot product

若是两个归一化后的向量的点积结果等于 1,则意味着这两个向量的方向相同。咱们建立了 areEqual 函数用来比较两个浮点数:

const EPSILON = 0.00000001

const areEqual = (one, other, epsilon = EPSILON) =>
  Math.abs(one - other) < epsilon

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  haveSameDirectionWith(other) {
    const dotProduct = this.normalize().dotProduct(other.normalize())
    return areEqual(dotProduct, 1)
  }
}

const one = new Vector(2, 4)
const other = new Vector(4, 8)
console.log(one.haveSameDirectionWith(other))
// true
复制代码

若是两个归一化后的向量点积结果等于 -1,则表示它们的方向彻底相反:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  haveOppositeDirectionTo(other) {
    const dotProduct = this.normalize().dotProduct(other.normalize())
    return areEqual(dotProduct, -1)
  }
}

const one = new Vector(2, 4)
const other = new Vector(-4, -8)
console.log(one.haveOppositeDirectionTo(other))
// true
复制代码

若是两个归一化后的向量的点积结果为 0,则表示这两个向量是相互垂直的:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  isPerpendicularTo(other) {
    const dotProduct = this.normalize().dotProduct(other.normalize())
    return areEqual(dotProduct, 0)
  }
}

const one = new Vector(-2, 2)
const other = new Vector(2, 2)
console.log(one.isPerpendicularTo(other))
// true
复制代码

叉积

叉积仅对三维向量适用,它会产生垂直于两个输入向量的向量:

咱们实现叉积时,假定它只用于计算三维空间内的向量。

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  // 只适用于 3 维向量
  crossProduct({ components }) {
    return new Vector(
      this.components[1] * components[2] - this.components[2] * components[1],
      this.components[2] * components[0] - this.components[0] * components[2],
      this.components[0] * components[1] - this.components[1] * components[0]
    )
  }
}

const one = new Vector(2, 1, 1)
const other = new Vector(1, 2, 2)
console.log(one.crossProduct(other))
// Vector { components: [ 0, -3, 3 ] }
console.log(other.crossProduct(one))
// Vector { components: [ 0, 3, -3 ] }
复制代码

其它经常使用方法

在现实生活的应用中,上述方法是远远不够的。好比说,咱们有时须要找到两个向量的夹角、将一个向量反向,或者计算一个向量在另外一个向量上的投影等。

在开始编写上面说的方法前,须要先写下面两个函数,用于在角度与弧度间相互转换:

const toDegrees = radians => (radians * 180) / Math.PI
const toRadians = degrees => (degrees * Math.PI) / 180
复制代码

夹角

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  angleBetween(other) {
    return toDegrees(
      Math.acos(
        this.dotProduct(other) /
        (this.length() * other.length())
      )
    )
  }
}

const one = new Vector(0, 4)
const other = new Vector(4, 4)
console.log(one.angleBetween(other))
// 45.00000000000001
复制代码

反向

当须要将一个向量的方向指向反向时,咱们能够对这个向量进行 -1 缩放:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  negate() {
    return this.scaleBy(-1)
  }
}

const vector = new Vector(2, 2)
console.log(vector.negate())
// Vector { components: [ -2, -2 ] }
复制代码

投影

project v on d

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  projectOn(other) {
    const normalized = other.normalize()
    return normalized.scaleBy(this.dotProduct(normalized))
  }
}

const one = new Vector(8, 4)
const other = new Vector(4, 7)
console.log(other.projectOn(one))
// Vector { components: [ 6, 3 ] }
复制代码

设定长度

当须要给向量指定一个长度时,可使用以下方法:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  withLength(newLength) {
    return this.normalize().scaleBy(newLength)
  }
}

const one = new Vector(2, 3)
console.log(one.length())
// 3.6055512754639896
const modified = one.withLength(10)
// 10
console.log(modified.length())
复制代码

判断相等

为了判断两个向量是否相等,能够对它们对应的份量使用 areEqual 函数:

class Vector {
  constructor(...components) {
    this.components = components
  }
  // ...
  
  equalTo({ components }) {
    return components.every((component, index) => areEqual(component, this.components[index]))
  }
}

const one = new Vector(1, 2)
const other = new Vector(1, 2)
console.log(one.equalTo(other))
// true
const another = new Vector(2, 1)
console.log(one.equalTo(another))
// false
复制代码

单位向量与基底

咱们能够将一个向量看作是“在 x 轴上走 v_x 的距离、在 y 轴上走 v_y 的距离、在 z 轴上走 v_z 的距离”。咱们可使用 \hat { \imath }\hat { \jmath }\hat { k } 分别乘上一个值更清晰地表示上述内容。下图分别是 xyz 轴上的单位向量

\hat { \imath } = ( 1,0,0 ) \quad \hat { \jmath } = ( 0,1,0 ) \quad \hat { k } = ( 0,0,1 )

任何数值乘以 \hat { \imath } 向量,均可以获得一个第一维份量等于该数值的向量。例如:

2 \hat { \imath } = ( 2,0,0 ) \quad 3 \hat { \jmath } = ( 0,3,0 ) \quad 5 \hat { K } = ( 0,0,5 )

向量中最重要的一个概念是基底。设有一个 3 维向量 \mathbb{R}^3,它的基底是一组向量:\{\hat{e}_1,\hat{e}_2,\hat{e}_3\},这组向量也能够做为 \mathbb{R}^3 的坐标系统。若是 \{\hat{e}_1,\hat{e}_2,\hat{e}_3\} 是一组基底,则能够将任何向量 \vec{v} \in \mathbb{R}^3 表示为该基底的系数 (v_1,v_2,v_3)

\vec{v} = v_1 \hat{e}_1 + v_2 \hat{e}_2 + v_3 \hat{e}_3

向量 \vec{v} 是经过在 \hat{e}_1 方向上测量 v_2 的距离、在 \hat{e}_2 方向上测量 v_1 的距离、在 \hat{e}_3 方向上测量 v_3 的距离得出的。

在不知道一个向量的基底前,向量的系数三元组并无什么意义。只有知道向量的基底,才能将相似于 (a,b,c) 三元组的数学对象转化为现实世界中的概念(好比颜色、几率、位置等)。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索