資料結構-陣列

致于数据科学家的小陈發表於2024-03-26

陣列是一種最為常見和應用廣泛的資料結構, 不用語言的陣列不太一樣, 對於 js 的陣列來說, 和 python 的列表極其類似, 都是可動態擴充值和型別的, 還是來大致總結一下這個基礎資料結構.

建立陣列

let arr1 = new Array('a', 'b', 66)
let arr2 = ['a', 'b', 66]

console.log(arr1, arr2);

陣列訪問和迭代

  • 遞迴
  • for 索引
  • for / of 值
const fibArr = []
fibArr[1] = 1
fibArr[2] = 1

for (let i = 3; i < 20; i++) {
  fibArr[i] = fibArr[i-1] + fibArr[i-2]
}

for (let i = 1; i < fibArr.length; i++) {
  console.log(fibArr[i]);
}

console.log(fibArr);

更多場景是我們直接遍歷陣列元素, 類似的結構是: arr = [ {}, {}, {} ...] 推薦用 for ... of

const arr = [1, 2, 3, {'name': 'cj', age: 18}]

for (let item of arr) {
  console.log(item);
}
PS F:\algorithms> node .\array.js
1
2
3
{ name: 'cj', age: 18 }

新增元素

  • push
  • unshift
  • Array.prototype. xxx = function (value) =>
let arr = []

// 尾插
arr.push(666)
arr[arr.length] = 888

console.log(arr);

// 頭插
Array.prototype.insertHead = function(value) {
  for (let i = this.length; i >= 0; i--) {
    this[i] = this[i-1]
  }
  this[0] = value 
}

arr.unshift(2, 3)

arr.insertHead(111)
console.log(arr);
PS F:\algorithms> node .\array.js
[ 666, 888 ]
[ 2, 3, 111, 666, 888 ]

刪除元素

  • pop
  • shift
  • Array.prototype.xxx = function(value) =>
let arr = [1, 2, 3, 4, 'cj', 'nb']

// 尾刪
item = arr.pop()
// 頭刪
item2 = arr.shift()

console.log(item, item2, arr);
nb 1 [ 2, 3, 4, 'cj' ]

當然從頭部刪除也可以透過如下兩個步驟:

  • 建立一個 reIndex 的方法, 接收一個陣列, 然後過濾掉 undefined 的元素, 返回新陣列
  • 透過索引方式手動刪除原陣列第一個元素, 然後將這個結果丟到 reIndex 中即可
// 將陣列中的中 undefined 元素過濾
Array.prototype.reIndex = function (arr) {
  const newArr = []
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] != undefined) newArr.push(arr[i])
  }
  return newArr
}

// 手動刪除第一個元素並排序
Array.prototype.removeHead = function() {
  for (let i = 0; i < this.length; i++) {
    this[i] = this[i+1]
  }
  return this.reIndex(this)
}


let arr = [1, 2, 3, 4, 'cj', 'nb']
console.log(arr.removeHead());

[ 2, 3, 4, 'cj', 'nb' ]

在任意位置新增或者刪除元素

  • splice (start, deleteCount, item1, item2, ...))
  • 可增, 可刪, 可替換

兩個值的時候, 就是刪除

arr = [1, 2, 3, 4, 5, 6]

// 從索引 2 的位置, 刪除 2個元素
arr.splice(2, 2)

console.log(arr)
[ 1, 2, 5, 6 ]

多個值的時候, 可以是新增, 比如在索引為 2 的位置, 往前新增兩個元素, 10, 11

arr = [1, 2, 3, 4, 5, 6]

// 索引為 2 的位置, 往前新增兩個元素, 10, 11
arr.splice(2, 0, 10, 11)

console.log(arr)
[ 1, 2, 10, 11, 3, 4,  5,  6 ]

同樣, 如果是要將索引為 2 的位置刪掉, 再替換也是一樣的, deleteCount 設定為 1 即可.

arr.splice(2, 1)         // 表示刪掉索引為 2 的元素
arr.splice(2, 1, 10, 11) // 表示刪掉索引為 2 的元素, 並原地插入 10, 11

多維陣列

類似, 矩陣, 多層級的 json 等都基本可以透過一維陣列方式實現, 在實際應用中用得最多的就是二維陣列. 這裡我們以矩陣來做一個演示.

以兩天監測的平均華氏氣溫為例:

let avgTempDay01 = [ 72, 75, 79, 79, 81, 81]
let avgTempDay02 = [ 81, 79, 75, 75, 73, 72]

顯然這樣來儲存是比較分散的, 透過矩陣或者二維陣列的方式則更好一些.

let avgTempArr = []
// day01
avgTempArr[0] = []
avgTempArr[0][0] = 72
avgTempArr[0][1] = 75
avgTempArr[0][2] = 79
avgTempArr[0][3] = 79
avgTempArr[0][4] = 81
avgTempArr[0][5] = 81

// day02
avgTempArr[1] = []
avgTempArr[1][0] = 81
avgTempArr[1][1] = 79
avgTempArr[1][2] = 75
avgTempArr[1][3] = 75
avgTempArr[1][4] = 73
avgTempArr[1][5] = 72

console.log(avgTempArr);
PS F:\algorithms> node .\array.js
[ [ 72, 75, 79, 79, 81, 81 ], [ 81, 79, 75, 75, 73, 72 ] ]

當然也可以簡寫成這樣:


let avgTempArr = []

avgTempArr[0] = [ 72, 75, 79, 79, 81, 81]
avgTempArr[1] = [ 81, 79, 75, 75, 73, 72]

二維陣列迭代

  • 巢狀 for 迴圈即可, 先行後列的方式
function printMatrix(matrix) {
  for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
      console.log('pos:', [i, j], matrix[i][j])
    }
  }
}


let avgTempArr = []
avgTempArr[0] = [ 72, 75, 79, 79, 81, 81]
avgTempArr[1] = [ 81, 79, 75, 75, 73, 72]

console.log(avgTempArr);
printMatrix(avgTempArr)
PS F:\algorithms> node .\array.js
[ [ 72, 75, 79, 79, 81, 81 ], [ 81, 79, 75, 75, 73, 72 ] ]

pos: [ 0, 0 ] 72
pos: [ 0, 1 ] 75
pos: [ 0, 2 ] 79
pos: [ 0, 3 ] 79
pos: [ 0, 4 ] 81
pos: [ 0, 5 ] 81
pos: [ 1, 0 ] 81
pos: [ 1, 1 ] 79
pos: [ 1, 2 ] 75
pos: [ 1, 3 ] 75
pos: [ 1, 4 ] 73
pos: [ 1, 5 ] 72

陣列常用方法

在JavaScript裡,陣列是經過改進的物件,這意味著建立的每個陣列都有一些可用的方法。陣列很有趣,因為它十分強大,和 Python 裡面的列表類似的. 且相比其他語言中的陣列,JavaScript中的陣列有許多很好用的方法.

常用陣列方法 描述 示例
push / pop 在尾部新增, 刪除元素 arr.push(1, 2); item = arr.pop()
unshift / shift 在頭部新增, 刪除元素 arr.unshift(); arr.shift()
concat 拼接2個或多個陣列, 並返回截個屏 arr1.concat(arr2, arr3)
join 將陣列元素拼接為一個字串 arr.join( ) 通常配合字元的 split 用
indexOf 查詢元素第一次出現的索引, 沒有則 -1 arr.indexOf('item')
slice 查詢索引範圍內的元素, 前閉後開 arr.slice(1, 3) 返回索引為 1, 2 的元素
reverse 將陣列元素反序, 原地修改 arr.reverse()
sort 不傳參按字母排序, 傳參(函式) 按函式排 arr.sort((a, b) => b - a) 降序
map 傳回撥函式處理每個元素, 返回新陣列 arr2 = arr.map((item) => item * 2) 每個元素平方
filter 傳回撥呼聲判斷每個元素結果為 true 的, 返回新陣列 arr2 = arr.filter((item) => item % 2 == 0) 只要偶數
forEach 傳回撥函式, 遍歷陣列處理, 無返回值 arr.forEach((item, index) => console.log(item, index))
every / some 傳回撥函式, 判斷每個元素, 存在 / 所有 返回 Boolean arr.some((item) => item % 2 == 0 )
includes 判斷是否存在某個元素, 返回 Boolean arr.includes('item') 感覺和 indexOf 也差不多
from / ... 對陣列的淺複製 arr2 = arr.from(arr) ; arr2 = [...arr]

陣列/物件的深複製

陣列是引用型別, 在更多應用中會將一些資料包裝成陣列或物件形式傳給後端介面, 通常就淺複製就行, 如果不會在多個應用中共用的話:

  • arr2 = [ ...arr ]
  • arr2 = Array.from(arr)
  • arr2 = arr.slice()
  • arr2 = arr.map(item => item)

就淺複製不勝列舉. 但我們更多的是關注深複製.

第一種深複製的簡單粗暴方法是用 JSON.parse(JSON.stringify( ) ) 即先將陣列序列化字串, 再進行反序列化成陣列.

const arr = [1, 2, 3, {'name': 'cj', age: 18}]

const arr2 = JSON.parse(JSON.stringify(arr))

arr[3].name = 'ccc'
console.log('arr: ', arr)
console.log('arr2: ', arr2)
PS F:\algorithms> node .\array.js
arr:  [ 1, 2, 3, { name: 'ccc', age: 18 } ]
arr2:  [ 1, 2, 3, { name: 'cj', age: 18 } ]

第二種方法是透過 遞迴 的方式層層遍歷陣列元素:

  • 先定義一個空陣列, 或者空物件
  • 遍歷陣列元素, 判斷當型別為 Array 時候, 再繼續遞迴遍歷
  • 判斷當型別為 Object 時候, 再繼續遞迴遍歷
  • 當為普通元素時, 新增進陣列 (遞迴終止條件)
function deepClone(obj) {
  // 先要判斷 obj 是陣列, 物件, 還是基本型別
  // 優先陣列處理, 因為陣列也是物件

  if (Array.isArray(obj)) {
    // 陣列
    var ret = []
    for (var item of obj) {
      ret.push(deepClone(item))
    }
  } else if (typeof obj == 'object') {
    // 物件
    var ret = {}
    for (var key in obj) {
      ret[key] = deepClone(obj[key])
    }
  } else {
    // 基本型別直接輸出, 作為遞迴出口
    var ret = obj
  }
  return ret 
}


const arr = [1, [{'a': 55}, 9], 3, {'name': 'cj', age: 18}]

const arr2 = deepClone(arr)

arr[3].name = 'ccc'
console.log('arr: ', arr)
console.log('arr2: ', arr2)

PS F:\algorithms> node .\array.js
arr:  [ 1, [ { a: 55 }, 9 ], 3, { name: 'ccc', age: 18 } ]
arr2:  [ 1, [ { a: 55 }, 9 ], 3, { name: 'cj', age: 18 } ]

陣列排序

先是反序的 reverse 原地方法.

const arr = [1, 2, 3, 'cj']
arr.reverse()
console.log(arr);
PS F:\algorithms> node .\array.js
[ 'cj', 3, 2, 1 ]

然後是陣列排序的 sort 方法. 當不傳函式時候, 就預設以字元排序, 注意不是數值哦, 一定要記得穿一個比較函式進去!!!

const arr = [1, 2, 80, 9]
arr.sort()
console.log(arr);
PS F:\algorithms> node .\array.js
[ 1, 2, 80, 9 ]

預設是字元排序, 所以進行升序或者降序的時候, 必須要傳入一個compareFn .

const arr = [1, 2, 3, 1, 0, 'cj']
// a -b 升序預設, b-a 降序
arr.sort((a, b) => b -a )

console.log(arr);
PS F:\algorithms> node .\array.js
[ 3, 2, 1, 1, 0, 'cj' ]

至此, 關於 javascript 的陣列結構就差不多到這了.

相關文章