點燈坊

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

使用 filter() 過濾 Array 中符合條件 Element

Sam Xiao's Avatar 2020-03-01

filter() 亦是 FP 常用的 Higher-order Function,事實上我們也可自行實作 filter()練習 FP 基本功。

Version

macOS Catalina 10.15.3
VS Code 1.42.1
Quokka 1.0.289
Ramda 0.27.0

Imperative

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let filter = f => arr => {
  let result = []

  for(let x of arr)
    if (f(x)) 
      result.push(x)

  return result
}

let f = v => arr => filter(x => x.price === v)(arr)

f(100)(data) // ?

第 1 行

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

我們只想過濾出 price100 的資料。

第 7 行

let filter = f => arr => {
  let result = []

  for(let x of arr)
    if (f(x)) 
      result.push(x)

  return result
}

Imperative 會先建立 result empty array,使用 for loop 對 data 一筆一筆處理,當 f(x) 結果為 true 時,將 x push 進 result array 回傳。

filter000

ECMAScript

reduce()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let filter = f => arr => arr.reduce((a, x) => f(x) ? [...a, x] : a, [])

let f = v = arr => filter(x => x.price === v)(arr)

filter(x => x.price === 100)(data) // ?

只要能使用 for loop,就能使用 reduce() 改寫。

filter001

filter()

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = v => arr => arr.filter(x => x.price === v)

f(100)(data) // ?

ECMAScript 已經內建 filter(),可直接使用。

filter002

Ramda

import { filter } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = v => filter(x => x.price === v)

f(100)(data) // ?

Ramda 亦提供 filter(),與內建不同的是 data 放在最後一個 argument,方便 function composition。

filter()
(a -> Boolean) -> [a] -> [a]
從 Array 過濾出想要的 Element

(a -> Boolean):過濾條件 predicate

[a]:data 為 array

[a]:回傳為過濾過 array

filter003

Point-free

propEq()

import { filter, propEq } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = v => filter(propEq('price', v))

f(100)(data) // ?

filter() 的 predicate 可由 propEq() 產生使 predicate 能 point-free。

filter004

import { filter, propEq, compose } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = compose(filter, propEq('price'))

f(100)(data) // ?

也可使用 compose() 組合 filter()propEq() 使 f() 能 point-free。

filter005

where()

import { filter, where, equals } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = v => filter(where({
  price: equals(v)
}))

f(100)(data) // ?

filter() 的 predicate 另外一種用法是搭配 where(),尤其對 data 是 array of object 時非常好用。

filter006

import { filter, where, equals, compose, objOf } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = compose(filter, where, objOf('price'), equals)

f(100)(data) // ?

若要使 f() 也 point-free,可使用 compose() 從內層拆起,組合 filter()where()objOf()eqauls()

filter007

Conclusion

  • filter() 雖然用起來很直覺,也可使用 imperative 與 reduce() 實作,事實上 imperative 要重構時,與自行實作 filter() 思考過程類似
  • 當 predicate 也 point-free 後,就更能看出 function composition 雛形,可使用 compose()filter() 與 callback 組合起來
  • 當要使用 compose() 時,只要從內側一步一步向外拆解 function,就能順利寫出 compose()

Reference

Ramda, filter()
Ramda, propEq()
Ramda, filter()
Ramda, compose()
Ramda, where()
Ramda, equals()
Ramda, objOf()