跟著 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
的元素。