詳解JS中的reduce fold unfold用法
說說reduce吧, 很喜歡這個函數,節省了不少代碼量,而且有一些聲明式的雛形了,一些常見的工具函數,flatten,deepCopy,mergeDeep等用reduce實現的很優雅簡潔。reduce也稱為fold,本質上就是一個折疊數組的過程,把數組中的多個值經過運算變成一個值,每次運算都會有一個函數處理,這個函數就是reduce的核心元素,稱之為reducer,reducer函數是個2元函數,返回1個單值,常見的add函數就是reducer
const addReducer = (x, y) => x + y;
這個add函數就是一個reducer,最常見的用法就是結合數組的reduce方法來用
[1, 2, 3, 4, 5].reduce(addReducer, 0) // 15
為了更好的理解reduce,下面用不同的思路實現一遍這個函數
使用for...ofconst reduce = (f, init, arr) => { let acc = init; for (const item of arr) { acc = f(acc, item); } return acc}// 執行reduceFor(addReducer, 0, [1, 2, 3, 4, 5]) // 15使用while循環
reduce = (f, init, arr) => { let acc = init; let current; let i = 0; while ((current = arr[i++])) { acc = f(acc, current); } return acc;}// 執行reduceFor(addReducer, 0, [1, 2, 3, 4, 5]) // 15更像fold的實現
上面的實現也都好理解,但好像沒有體現出來折疊(fold)這個過程,折疊應該是對數組的層層擠壓操作,上面的實現數組和邏輯其實是分開了,而且也引入了比較多的中間變量,雖然是在內部沒有副作用吧。其實換個思路想一下,如果把狀態通過參數來傳遞,就可以更好的體現fold的過程,這里的參數是值是指逐漸變化的數組和計算值,并可以盡可能的做到無狀態,真正純函數的實現是沒有表達式,只有語句的,這個可以用遞歸做到。下面的實現是用尾遞歸實現的reduce,可以在實現的過程中就看出數組和計算值是怎樣變化的。非常符合fold這個稱謂
function reduce(f, init, arr) { if (arr.length === 0) return init; const [head, ...rest] = arr; return reduceRecursion(f, f(init, head), rest);}// 執行reduceFor(addReducer, 0, [1, 2, 3, 4, 5]) // 15unfold
fold反過來就是unfold,unfold顧名思義就是根據一個反過來的reducer,來生成一系列的值。此時這個如果說原來的reducer實現類似于(a, b) -> c,那反過來就是c -> [a, b], 生成序列是一個很基本的操作,但就是這個基本的操作,也有很多實現的思路,在介紹unfold之前,看一下實現序列的其他方法,最后來做一個對比。
序列的實現
range(0, 100, 5)
期待結果
[0, 5, 10, ... 95]
數組實現這個就不多說了,大家應該都知道。
range = (first, last, step) => { const n = (last - first) / step + 1; return Array.from({ length: n - 1 }) .map((_, index) => first + index * step);}// 也可以使用from的第二個參數// Array.from({ length: n }, (_, i) => first + i * step);生成器實現
生成序列還有一個利器,那就是generator,生成器生成器,就是用來生成數據的。generator返回一個迭代器,也很容易生成序列
function* range(first, last, step) { let acc = first; while (acc < last) { yield acc; acc = acc + step; }}[...range(0, 100, 5)]
兩者相比,generator更注重生成的過程,Array注重數據變化的過程。
unfold實現在實現unfold之前,首先梳理一下實現思路,和fold一樣,也是用遞歸,且要在實現的過程中看到對應數據的變化。大體過程如下
0 -> [0, 5]
5 -> [5, 10]
10 -> [10, 15]
15 -> [15, 20]
...
90 -> [90, 95]
95 -> [95, 100]
可以看出過程恰恰是fold反過來,符合c -> [a, b]因為初始值肯定為一個數組,所以unfold只需要兩個參數,實現如下。
function unfold(f, init) { const g = (f, next, acc) => { const result = f(next); const [head, last] = result || []; console.log(last); return result ? g(f, last, acc.concat(head)) : acc; }; return g(f, init, []);}range = R.curry((first, last, step) => unfold(next => next < last && [next, next + step], 0))// 執行range(0, 100, 5)總結
以上就是結合reduce和一個生成序列的例子簡單介紹了一下fold和unfold這兩個在fp編程中很重要的概念,當然他們功能不只是生成序列,還有很多很強大的功能
以上就是詳解JS中的reduce fold unfold用法的詳細內容,更多關于JS的資料請關注好吧啦網其它相關文章!
相關文章:
