點燈坊

失くすものさえない今が強くなるチャンスよ

使用 intersperse() 對 Array 中每筆資料間新增資料

Sam Xiao's Avatar 2020-01-22

實務上有時需要在每一筆資料間新增顯示資料,如 Divider,Ramda 已經內建 intersperse(),可直接使用。

Imperative

let data = [1, 2, 3]

let intersperse = separator => arr => {
  let result = []

  for(let i = 0; i < arr.length; i++) {
    if (i === arr.length - 1)
      result.push(arr[i])
    else
      result.push(arr[i], separator)
  }

  return result
}

intersperse(0)(data) // ?

若想在 [1, 2, 3] 之間加上 0,成為 [1, 0, 2, 0, 3],imperative 會判斷只要不是最後一筆就會加上 0,若最後一筆就不加。

intersperse002

Reduce()

let data = [1, 2, 3]

let intersperse = separator => arr => arr.reduce(
  (a, x, i) => (i === arr.length - 1) ? [...a, x] : [...a, x, separator]
, [])

intersperse(0)(data) // ?

Imperative 也能使用 reduce() 加以改寫。

intersperse003

Functional

import { map, pipe, flatten, dropLast } from 'ramda'

let data = [1, 2, 3]

let intersperse = separator => pipe(
  map(x => [x, separator]),
  flatten,
  dropLast(1)
)

intersperse(0)(data) // ?

若以 Functional 角度思考:

  • 每一筆資料加上 0 之後,從一筆變兩筆
  • 刪除最後一筆資料

第 6 行

map(x => [x, separator]),

使用 map() 將每一筆資料變成兩筆,回傳為 array。

12 行

flatten,

因為每一筆都是 array,所以變成了如下的兩層 array。

[ [ 1, 0 ], [ 2, 0 ], [ 3, 0 ] ]

這並不是我們要的,因此須使用 flatten() 加以攤平。

13 行

dropLast(1)

因為最後一筆 0 不是我們要的,所以使用 dropLast(1) 刪除之。

我們發現使用 FP 後,透過 pipe() 組合 map()flatten()dropLast(),整個演算法清楚可見,不必如 imperative 須 trace code 才知道演算法為何。

intersperse000

Point-free

import { map, pipe, flatten, dropLast, pair, __ } from 'ramda'

let data = [1, 2, 3]

let intersperse = separator => pipe(
  map(pair(__, separator)),
  flatten,
  dropLast(1)
)

intersperse(0)(data) // ?

第 6 行

map(pair(__, separator)),

由於 map() 主要產生新的 array,可改由 pair() higher order function 產生 mapper function。

intersperse003

chain()

import { chain, pipe, dropLast, pair, __ } from 'ramda'

let data = [1, 2, 3]

let intersperse = separator => pipe(
  chain(pair(__, separator)),
  dropLast(1)
)

intersperse(0)(data) // ?

map() + flatten() 組合在 ECMAScript 稱為 flatMap(),在 Ramda 稱為 chain(),可使用 chain() 加以取代。

intersperse004

Ramda

import { intersperse } from 'ramda'

let data = [1, 2, 3]

intersperse(0)(data) // ?

Ramda 已經內建 intersperse(),可直接使用。

intersperse()
a → [a] → [a]
對 array 中每筆資料間新增一筆資料

a:要新增的資料

[a]:data 為 array

[a]:新增資料後的 array

intersperse001

Conclusion

  • Ramda 的 255 個 function 中,一開始可能不知道所有 function 功能,可先使用已知 function 組合,隨著經驗累積,就可使用功能更強 function

Reference

Ramda, intersperse()