跟著 Ramda 學 FP - 剪裁陣列(2)
ramda drop dropLast dropWhile dropLastWhile take takeLast takeWhile takeLastWhile -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
take 和 drop 有相同的簽名 Number → [a] → [a]。
take 是從陣列頭開始拿幾個陣列元素。
drop 是從陣列頭開始丟幾個陣列元素。
takeLast or dropLast
takeLast 和 dropLast 也有相同的簽名 Number → [a] → [a]。
takeLast 是從陣列尾開始拿幾個陣列元素。
dropLast 是從陣列尾開始丟幾個陣列元素。
在 ramda 中,
drop(1) 有一個簡化參數的版本 tail;而 dropLast(1) 有一個簡化版本 init。
但 take(1) 和 takeLast(1) 並沒有相同的簡化版本。
但有一個類似的函式,分別叫 head 和 last。
這兩個函式其實是 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]。
與 take,takeLast,drop 和 dropLast 的簽名差異在
前者第一個參數是一個函式 (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)
-
原函式是
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的元素。