The Issue of the Day Before

跟著 Ramda 學 FP - 剪裁陣列(2)

ramda -

Ramda 提供一系列對陣列的操作。最簡單的就是剪裁陣列。 整理如下,

fn

reverse

while

reverse + While

tail

drop

dropLast

dropWhile

dropLastWhile

init

take

takeLast

takeWhile

takeLastWhile

Why

雖然這些函式功能相對的簡單,但正因他簡單而經常用到,所以 ramda 才會他們納入。

What

take

從陣列頭開始n 個陣列元素。

drop

從陣列頭開始n 個陣列元素。

takeLast

從陣列開始n 個陣列元素。

dropLast

從陣列開始n 個陣列元素。

takeWhile

從陣列頭開始取元素直到判斷式傳回 false,傳回內容不包含當前判斷為 false 的元素。

dropWhile

從陣列頭開始棄元素直到判斷式傳回 false,傳回內容不包含當前判斷為 false 的元素。

takeLastWhile

從陣列開始取元素直到判斷式傳回 false,傳回內容不包含當前判斷為 false 的元素。

dropLastWhile

從陣列開始棄元素直到判斷式傳回 false,傳回內容不包含當前判斷為 false 的元素。

How

take or drop

takedrop 有相同的簽名 Number → [a] → [a]

take 是從陣列頭開始幾個陣列元素。

drop 是從陣列頭開始幾個陣列元素。

takeLast or dropLast

takeLastdropLast 也有相同的簽名 Number → [a] → [a]

takeLast 是從陣列開始幾個陣列元素。

dropLast 是從陣列開始幾個陣列元素。

ramda 中,

drop(1) 有一個簡化參數的版本 tail;而 dropLast(1) 有一個簡化版本 init

take(1)takeLast(1) 並沒有相同的簡化版本。 但有一個類似的函式,分別叫 headlast

這兩個函式其實是 nth(0)nth(-1) 的包裝。

可以寫為,

const tail = drop(1)
const init = dropLast(1)
const head = nth(0)
const last = nth(-1)

但不管是 take, drop, takeLast 還是 dropLast,其實都是 slice 函式的再包裝。

const take = (n) => slice(0, n)
const drop = (n) => slice(n, Infinity)
const takeLast = (n) => compose(drop, flip(subtract)(n), length)
const dropLast = (n) => compose(take, flip(subtract)(n), length)

上面是簡單的重定義四個函式,但其中並未考慮 n 大於陣列長度或為負數的情況。

takeWhile, takeLastWhile, dropWhile or dropLastWhile

上面四個函式,的簽名都是 (a -> Boolean) -> [a] -> [a]。 與 taketakeLastdropdropLast 的簽名差異在 前者第一個參數是一個函式 (a -> Boolean) 而後者是一個數字 Number

所以,可以簡單的猜測 takeWhile 透過函式 fn(a -> Boolean) 得到一個數字,再呼叫 take 即可得到等效的 takeWhile

// takeWhile :: ((a -> Boolean) -> [a]) -> [a] // (1)
const takeWhile = (fn, xs) => chain(take, fn)(xs)
// chain(take, fn)(xs)  => take(fn(xs), xs)
  1. 原函式是 curry 化的,但這邊先不處理。

    由 `+take

    (Number, [a])` 推理可知,`fn` 需要一個陣列(`[a]`)輸入和一個數字輸出(`+Number),所以這裡的 `fn 簽名是 ([a] -> Number) 與原本的需求不合。

所以我們需要一個能將 (a -> Boolean) 轉成 ([a] -> Number) 的方法。

例如,

const _fn = (fn, xs) => {
  const len = xs.length
  let i = 0
  while (i < len && fn(xs[i])) {
    i += 1;
  }
  return i
}

這能在陣列元素符合判斷函式時傳回不符合判斷的元素個數。 恰巧 Ramda 也有一個適合的函式 findIndex,他的簽名是 (a -> Boolean) -> [a] -> Number,也就是我們需要的。

可以改寫如下,

// _fn :: (a -> Boolean) -> [a] -> Number
const _fn = curryN(2, compose(inc, findIndex))

最後可以改寫為,

const takeWhile = (fn, xs) => chain(take, _fn(fn))(xs)
// 等效的 curry 函式
// converge(take, [_fn, flip(I)])

其他三個函式簽名跟 takeWhile 一樣,他們功能分別是:

  • takeWhile 是從陣列頭開始直到判斷式為 false,不包含當前為 false 的元素。

  • takeLastWhile 是從陣列開始直到判斷式為 false,不包含當前為 false 的元素。

  • dropWhile 是從陣列頭開始直到判斷式為 false,不包含當前為 false 的元素。

  • dropLastWhile 是從陣列開始直到判斷式為 false,不包含當前為 false 的元素。

閱讀在雲端