利用遞迴來回壓平陣列

讓我豆頁痛的遞迴。

常犯的錯誤

使用 join()split()

我一開始想到的也是這種作法,但後來才知道這種做法會有問題:

1
2
3
4
5
6
function flatten(arr) {
return arr.join(',').split(',')
}
console.log(flatten([1, 2, 3])) // ['1', '2', '3']
console.log(flatten([1, 2, 3, [1, 2], [3, 4], 5])) // ['1', '2', '3', '1', '2', '3', '4', '5']
console.log(flatten([1, 2, 3, [1, [2]], [3, 4, [5]], 6])) // ['1', '2', '3', '1', '2', '3', '4', '5', '6']

(最後會變成字串是因為 join 會強制把元素轉成字串)

其實這個做法在大部分的情況下都沒有問題,唯獨在元素剛好是 「,」 的時候會出問題。

1
2
3
4
function flatten(arr) {
return arr.join(',').split(',')
}
console.log(flatten(['1,', 2, 3])) // ['1', '', '2', '3']

原因是因為:

  1. join 後會變成 => '1,,2,3'
  2. split 後會變成 => ['1', '', '2', '3']

忘記這兩個的作用可以參考 陣列的內建函式跟字串有關的內建函式

所以這個方法對會出現逗號的 case 都無法使用。

正確的作法

利用遞迴的概念來實作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function flatten(arr) {
// 儲存壓平後的陣列
const result = []
// 遍歷陣列元素
arr.forEach(item => {
// 如果元素是陣列,遞迴這個 function
if(Array.isArray(item)) {
// 呼叫自己
const flattenArray = flatten(item)
// 把壓平後的陣列 push 回結果
flattenArray.forEach(item => result.push(item))
} else {
result.push(item)
}
})
// 回傳結果
return result
}

console.log(flatten([1, 2, 3])) // [1, 2, 3]
console.log(flatten([1, 2, 3, [1, 2], [3, 4], 5])) // [1, 2, 3, 1, 2, 3, 4, 5]
console.log(flatten([1, 2, 3, [1, [2]], [3, 4, [5]], 6])) // [1, 2, 3, 1, 2, 3, 4, 5, 6]
console.log(flatten(['1,', 2, 3])) // ['1,', 2, 3]

這題的思路是這樣子:

  1. 建立一個 result,用來儲存壓平後的陣列
  2. 遍歷傳入的 arr,如果元素不是陣列,直接推入 result
  3. 如果是陣列,再把它壓平

重點在於第三步的時候,你會想說「這不就是我正要做的事情嗎?」,所以會感到很疑惑。

但其實不用想的太複雜,你只要知道 flatten 這個 function 最後就是會回傳一個「壓平後的陣列」,所以直接給它呼叫下去就對了。不管這個過程會重複呼叫幾次 flatten,最後都一定會有盡頭(當元素不再是陣列的時候,就會被推到 result)。

mentor-program-day22 reduce 的其他玩法
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×