国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

我是怎樣用函數(shù)式JavaScript計算數(shù)組平均值的

renweihub / 1107人閱讀

摘要:簡單模式記錄多個累加值在之前的版本中,我們創(chuàng)建了很多中間變量,。接下來,我們給自己設(shè)一個挑戰(zhàn),使用鏈式操作,將所有的函數(shù)調(diào)用組合起來,不再使用中間變量。你甚至可以繼續(xù)簡化上述代碼,移除不必要的中間變量,讓最終的計算代碼只有一行。

譯者按: 有時候一個算法的直觀、簡潔、高效是需要作出取舍的。

原文: FUNCTIONAL JAVASCRIPT: FIVE WAYS TO CALCULATE AN AVERAGE WITH ARRAY REDUCE

譯者: Fundebug

本文采用意譯,版權(quán)歸原作者所有

函數(shù)式編程中用于操作數(shù)組的方法就像“毒品”一樣,它讓很多人愛上函數(shù)式編程。因為它們真的十分常用而且又超級簡單。 .map().filter()都僅需一個參數(shù),該參數(shù)定義操作數(shù)組每一個元素的函數(shù)即可。reduce()會復(fù)雜一些,我之前寫過一篇文章介紹為什么人們難以掌握reduce()方法,其中一個原因在于很多入門資料都僅僅用算術(shù)作為例子。我寫了很多用reduce()來做算術(shù)以外的例子。

reduce()來計算數(shù)組的平均值是一個常用的模式。代碼看起來非常簡單,不過在計算最終結(jié)果之前你需要做兩個準備工作:

數(shù)組的長度

數(shù)組所有元素之和

這兩個事情看起來都很簡單,那么計算數(shù)組的平均值并不是很難了吧。解法如下:

function average(nums) {
    return nums.reduce((a, b) => a + b) / nums.length;
}

確實不是很難,是吧?但是如果數(shù)據(jù)結(jié)構(gòu)變得復(fù)雜了,就沒那么簡單了。比如,數(shù)組里面的元素是對象,你需要先過濾掉某些對象,然后從對象中取出數(shù)字。這樣的場景讓計算平均值變得復(fù)雜了一點。

接下來我們處理一個類似的問題(從this Free Code Camp challenge獲得靈感),我們會提供 5 種不同的解法,每一種方法有各自的優(yōu)點和缺點。這 5 種方法也展示了 JavaScript 的靈活。我希望可以給你在使用reduce的實戰(zhàn)中一些靈感。

問題提出

假設(shè)我們有一個數(shù)組,記錄了維多利亞時代常用的口語。接下來我們要找出那些依然現(xiàn)存于 Google Books 中的詞匯,并計算他們的平均流行度。數(shù)據(jù)的格式是這樣的:

const victorianSlang = [
    {
        term: "doing the bear",
        found: true,
        popularity: 108
    },
    {
        term: "katterzem",
        found: false,
        popularity: null
    },
    {
        term: "bone shaker",
        found: true,
        popularity: 609
    },
    {
        term: "smothering a parrot",
        found: false,
        popularity: null
    },
    {
        term: "damfino",
        found: true,
        popularity: 232
    },
    {
        term: "rain napper",
        found: false,
        popularity: null
    },
    {
        term: "donkey’s breakfast",
        found: true,
        popularity: 787
    },
    {
        term: "rational costume",
        found: true,
        popularity: 513
    },
    {
        term: "mind the grease",
        found: true,
        popularity: 154
    }
];

接下來我們用 5 中不同的方法計算平均流行度值。

1. for 循環(huán)

初次嘗試,我們不使用reduce()。如果你對數(shù)組的常用函數(shù)不熟悉,用 for 循環(huán)可以讓你更好地理解我們要做什么。

let popularitySum = 0;
let itemsFound = 0;
const len = victorianSlang.length;
let item = null;
for (let i = 0; i < len; i++) {
    item = victorianSlang[i];
    if (item.found) {
        popularitySum = item.popularity + popularitySum;
        itemsFound = itemsFound + 1;
    }
}
const averagePopularity = popularitySum / itemsFound;
console.log("Average popularity:", averagePopularity);

如果你熟悉 JavaScript,上面的代碼理解起來應(yīng)該很容易:

初始化polularitySumitemsFound變量。popularitySum記錄總的流行度值,itemsFound記錄我們已經(jīng)找到的所有的條目;

初始化lenitem來幫助我們遍歷數(shù)組;

for 循環(huán)每一次增加i的值,直到循環(huán)n次;

在循環(huán)中,我們每次取出當前索引位置的條目vitorianSlang[i]

檢查該條目是否在 Google Books 中

如果在,獲取popularity并累加到popularitySum;

并遞增itemsFound

最后,用popularitySum除以itemsFound來計算平均值。

代碼雖然不是那么簡潔,但是順利完成了任務(wù)。使用數(shù)組迭代方法可以更加簡潔,接下來開始吧…..

2. 簡單模式: filter, map 和 sum

我們首先將這個問題拆分成幾個子問題:

使用fitler()找到那些在 Google Books 中的條目;

使用map()獲取流行度;

使用reuduce()來計算總的流行度;

計算平均值。

下面是實現(xiàn)代碼:

// 輔助函數(shù)
// ----------------------------------------------------------------------------
function isFound(item) {
    return item.found;
}

function getPopularity(item) {
    return item.popularity;
}

function addScores(runningTotal, popularity) {
    return runningTotal + popularity;
}

// 計算
// ----------------------------------------------------------------------------

// 找出所有isFound為true的條目
const foundSlangTerms = victorianSlang.filter(isFound);

// 從條目中獲取流行度值,返回為數(shù)組
const popularityScores = foundSlangTerms.map(getPopularity);

// 求和
const scoresTotal = popularityScores.reduce(addScores, 0);

// 計算平均值
const averagePopularity = scoresTotal / popularityScores.length;
console.log("Average popularity:", averagePopularity);

注意看addScores函數(shù)以及調(diào)用reduce()函數(shù)的那一行。addScores()接收兩個參數(shù),第一個runningTotal,我們把它叫做累加數(shù),它一直記錄著累加的總數(shù)。每訪問數(shù)組中的一個條目,我們都會用addScores函數(shù)來更新它的值。第二個參數(shù)popularity是當前某個元素的值。注意,第一次調(diào)用的時候,我們還沒有runningTotal的值,所以在調(diào)用reduce()的時候,我們給runningTotal初始化。也就是reduce()的第二個參數(shù)。

這個版本的代碼簡潔很多了,也更加的直觀。我們不再告訴 JavaScript 引擎如何循環(huán),如何對當前索引的值做操作。我們定義了很多小的輔助函數(shù),并且把它們組合起來完成任務(wù)。filter(),map()reduce()幫我們做了很多工作。上面的實現(xiàn)更加直觀地告訴我們這段代碼要做什么,而不是底層如何去實現(xiàn)。

3. 簡單模式 II: 記錄多個累加值

在之前的版本中,我們創(chuàng)建了很多中間變量:foundSlangTermspopularityScores。接下來,我們給自己設(shè)一個挑戰(zhàn),使用鏈式操作,將所有的函數(shù)調(diào)用組合起來,不再使用中間變量。注意:popularityScores.length變量需要用其它的方式來獲取。我們可以在addScores的累加參數(shù)中記錄它。

// 輔助函數(shù)
// ---------------------------------------------------------------------------------
function isFound(item) {
    return item.found;
}

function getPopularity(item) {
    return item.popularity;
}

// 我們使用一個對象來記錄總的流行度和條目的總數(shù)
function addScores({ totalPopularity, itemCount }, popularity) {
    return {
        totalPopularity: totalPopularity + popularity,
        itemCount: itemCount + 1
    };
}

// 計算
// ---------------------------------------------------------------------------------

const initialInfo = { totalPopularity: 0, itemCount: 0 };
const popularityInfo = victorianSlang
    .filter(isFound)
    .map(getPopularity)
    .reduce(addScores, initialInfo);

const { totalPopularity, itemCount } = popularityInfo;
const averagePopularity = totalPopularity / itemCount;
console.log("Average popularity:", averagePopularity);

我們在reduce函數(shù)中使用對象來記錄了totalPopularityitemCount。在addScores中,每次都更新itemCount的計數(shù)。

通過filtermapreduce計算的最終的結(jié)果存儲在popularityInfo中。你甚至可以繼續(xù)簡化上述代碼,移除不必要的中間變量,讓最終的計算代碼只有一行。

4. point-free 式函數(shù)組合

注意: 如果你不熟悉函數(shù)式語言或則覺得難以理解,請?zhí)^這部分!

如果你熟悉curry()compose(),接下來的內(nèi)容就不難理解。如果你想知道更多,可以看看這篇文章: ‘A Gentle Introduction to Functional JavaScript’. 特別是第三部分 。

我們可以使用compose函數(shù)來構(gòu)建一個完全不帶任何變量的代碼,這就叫做point-free的方式。不過,我們需要一些幫助函數(shù)。

// 輔助函數(shù)
// ----------------------------------------------------------------------------
const filter = p => a => a.filter(p);
const map = f => a => a.map(f);
const prop = k => x => x[k];
const reduce = r => i => a => a.reduce(r, i);
const compose = (...fns) => arg => fns.reduceRight((arg, fn) => fn(arg), arg);

// The blackbird combinator.
// See: https://jrsinclair.com/articles/2019/compose-js-functions-multiple-parameters/
const B1 = f => g => h => x => f(g(x))(h(x));

// 計算
// ----------------------------------------------------------------------------

// 求和函數(shù)
const sum = reduce((a, i) => a + i)(0);

// 計算數(shù)組長度的函數(shù)
const length = a => a.length;

// 除法函數(shù)
const div = a => b => a / b;

// 我們使用compose()來將函數(shù)組合起來
// compose()的參數(shù)你可以倒著讀,來理解程序的含義
const calcPopularity = compose(
    B1(div)(sum)(length),
    map(prop("popularity")),
    filter(prop("found"))
);

const averagePopularity = calcPopularity(victorianSlang);
console.log("Average popularity:", averagePopularity);

我們在compose中做了所有的計算。從后往前看,首先filter(prop("found"))篩選出所有在 Google Books 中的條目,然后通過map(prop("popularity"))獲取所有的流行度數(shù)值,最后使用 magical blackbird (B1) combinator 來對同一個輸入進行sumlength的計算,并求得平均值。

// All the lines below are equivalent:
const avg1 = B1(div)(sum)(length);
const avg2 = arr => div(sum(arr))(length(arr));
const avg3 = arr => sum(arr) / length(arr);
const avg4 = arr => arr.reduce((a, x) => a + x, 0) / arr.length;

不要擔心看不明白,上面主要是為大家演示有 4 種方式來實現(xiàn)average功能。這就是 JavaScript 的優(yōu)美之處。

相對來說,本文的內(nèi)容是有點極客的。雖然筆者之前深度使用函數(shù)式語言 Haskell 做過不少研究項目,對函數(shù)式頗有理解,但是 point-free 風格的代碼,我們是不建議在實際工程中使用的,維護成本會很高。我們Fundebug所有的代碼都要求直觀易懂,不推崇用一些奇淫技巧來實現(xiàn)。除非某些萬不得已的地方,但是一定要把注釋寫得非常清楚,來降低后期的維護成本。

5. 終極優(yōu)化: 一次計算出結(jié)果

之前所有的解法都可以很好地工作。那些使用reduce()的解法都有一個共同點,它們將大的問題拆解問小的子問題,然后通過不同的方式將它們組合起來。但是也要注意它們對數(shù)組遍歷了三次,感覺很沒有效率。如果一次就可以計算出來,才是最佳的方案。確實可以,不過需要一點數(shù)學運算。

為了計算 n 個元素的平均值,我們使用下面的公式:

那么,計算 n+1 個元素的平均值,使用同樣的公式(唯一不同的是 n 變成 n+1):

它等同于:

同樣等同于:

做點變換:

結(jié)論是,我們可以一直記錄當前狀態(tài)下的所有滿足條件的元素的平均值。只要我們知道之前所有元素的平均值和元素的個數(shù)。

// 求平均值
function averageScores({ avg, n }, slangTermInfo) {
    if (!slangTermInfo.found) {
        return { avg, n };
    }
    return {
        avg: (slangTermInfo.popularity + n * avg) / (n + 1),
        n: n + 1
    };
}

const initialVals = { avg: 0, n: 0 };
const averagePopularity = victorianSlang.reduce(averageScores, initialVals).avg;
console.log("Average popularity:", averagePopularity);

這個方法只需要遍歷一次就計算出平均值,缺點是我們做了更多的計算。每一次當元素滿足條件,都要做乘法和除法,而不是最后才做一次除法。不過,它使用了更少的內(nèi)存,因為沒有中間的數(shù)組變量,我們只是記錄了一個僅僅有兩個元素的對象。

這樣寫還有一個缺點,代碼一點都不直觀,后續(xù)維護麻煩。至少一眼看過去不能理解它是做什么的。

所以,到底哪一種方案才是最好的呢?視情形而定。也許你有一個很大的數(shù)組要處理,也許你的代碼需要在內(nèi)存很小的硬件上跑。在這些場景下,使用第 5 個方案最佳。如果性能不是問題,那么就算使用最低效的方法也沒問題。你需要選擇最適合的。

還有一些聰明的朋友會思考:是否可以將問題拆解為子問題,仍然只遍歷一次呢?是的,確實有。需要使用 transducer。

關(guān)于Fundebug

Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應(yīng)用實時BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網(wǎng)等眾多品牌企業(yè)。歡迎大家免費試用!

版權(quán)聲明

轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2019/06/05/5-ways-calculate-an-average-with-reduce/

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/104419.html

相關(guān)文章

  • JavaScript深入淺出第2課:函數(shù)是一等公民是什么意思呢?

    摘要:函數(shù)是一等公民,是什么意思呢我來與大家探討一下,拋磚引玉。對于來說,函數(shù)可以賦值給變量,也可以作為函數(shù)參數(shù),還可以作為函數(shù)返回值,因此中函數(shù)是一等公民。也就是說,函數(shù)為第一公民是函數(shù)式編程的必要條件。 摘要: 聽起來很炫酷的一等公民是啥? 《JavaScript深入淺出》系列: JavaScript深入淺出第1課:箭頭函數(shù)中的this究竟是什么鬼? JavaScript深入淺出第2課...

    wmui 評論0 收藏0
  • JavaScript || 函數(shù)

    摘要:每個函數(shù)表達式包括函數(shù)對象括號和傳入的實參組成。和作用都是動態(tài)改變函數(shù)體內(nèi)指向,只是接受參數(shù)形式不太一樣。在定義函數(shù)時,形參指定為一個對象調(diào)用函數(shù)時,將整個對象傳入函數(shù),無需關(guān)心每個屬性的順序。 函數(shù) JavaScript中,函數(shù)指只定義一次,但可以多次被多次執(zhí)行或調(diào)用的一段JavaScript代碼。與數(shù)組類似,JavaScript中函數(shù)是特殊的對象,擁有自身屬性和方法 每個函數(shù)對象...

    learn_shifeng 評論0 收藏0
  • 前端知識點整理

    摘要:難怪超過三分之一的開發(fā)人員工作需要一些知識。但是隨著行業(yè)的飽和,初中級前端就業(yè)形勢不容樂觀。整個系列的文章大概有篇左右,從我是如何成為一個前端工程師,到各種前端框架的知識。 為什么 call 比 apply 快? 這是一個非常有意思的問題。 作者會在參數(shù)為3個(包含3)以內(nèi)時,優(yōu)先使用 call 方法進行事件的處理。而當參數(shù)過多(多余3個)時,才考慮使用 apply 方法。 這個的原因...

    Lowky 評論0 收藏0
  • 前端知識點整理

    摘要:難怪超過三分之一的開發(fā)人員工作需要一些知識。但是隨著行業(yè)的飽和,初中級前端就業(yè)形勢不容樂觀。整個系列的文章大概有篇左右,從我是如何成為一個前端工程師,到各種前端框架的知識。 為什么 call 比 apply 快? 這是一個非常有意思的問題。 作者會在參數(shù)為3個(包含3)以內(nèi)時,優(yōu)先使用 call 方法進行事件的處理。而當參數(shù)過多(多余3個)時,才考慮使用 apply 方法。 這個的原因...

    snowLu 評論0 收藏0
  • JavaScript 函數(shù)編程到底是個啥

    摘要:函數(shù)是一等公民。其實閉包本身也是函數(shù)式編程的一個應(yīng)用。劣勢不能算是嚴格意義上的函數(shù)式語言,很多函數(shù)式編程的特性并沒有。 隨著大前端時代的到來,在產(chǎn)品開發(fā)過程中,前端所占業(yè)務(wù)比重越來越大、交互越來越重。傳統(tǒng)的老夫拿起JQuery就是一把梭應(yīng)付當下重交互頁面已經(jīng)十分乏力。于是乎有了Angular,React,Vue這些現(xiàn)代框架。 但隨之而來的還有大量的新知識新名詞,如MVC,MVVM,F(xiàn)l...

    denson 評論0 收藏0

發(fā)表評論

0條評論

renweihub

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<