[譯] 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索