摘要:前言在計(jì)算機(jī)領(lǐng)域,記憶是主要用于加速程序計(jì)算的一種優(yōu)化技術(shù),它使得函數(shù)避免重復(fù)演算之前已被處理過的輸入,而返回已緩存的結(jié)果。被執(zhí)行了不是素?cái)?shù),其他數(shù)字默認(rèn)是素?cái)?shù)。我們可以看出,如果從開始打印斐波那契數(shù)列,函數(shù)被執(zhí)行了次。
前言
在計(jì)算機(jī)領(lǐng)域,記憶(memoization)是主要用于加速程序計(jì)算的一種優(yōu)化技術(shù),它使得函數(shù)避免重復(fù)演算之前已被處理過的輸入,而返回已緩存的結(jié)果。 -- wikipedia
Memoization 的原理就是把函數(shù)的每次執(zhí)行結(jié)果都放入一個對象中,在接下來的執(zhí)行中,在對象中查找是否已經(jīng)有相應(yīng)執(zhí)行過的值,如果有,直接返回該值,沒有才真正執(zhí)行函數(shù)體的求值部分。在對象里找值是要比執(zhí)行函數(shù)的速度要快的。
另外,Memoization 只適用于確定性算法,對于相同的輸入總是生成相同的輸出,即純函數(shù)。
一、簡單實(shí)現(xiàn)通過 Memoization 的定義和原理,我們就可以初步實(shí)現(xiàn) Memoization 了。
let memoize = function(func) { let cache = {}; return function(key) { if (!cache[key]) cache[key] = func.apply(this, arguments); return cache[key]; } }
是不是很簡單~ 函數(shù)記憶其實(shí)就是利用閉包,將函數(shù)參數(shù)作為存儲對象的鍵(key),函數(shù)結(jié)果作為存儲對象的 value 值。
二、underscore 實(shí)現(xiàn)underscore 的源碼中有 Memoization 方法的封裝,它支持傳入一個 hasher 用來計(jì)算緩存對象 key 的計(jì)算方式。
_.memoize = function(func, hasher) { var memoize = function(key) { // 把存儲對象的引用拿出來,便于后面代碼使用 var cache = memoize.cache; // hasher 是計(jì)算 key 值的方法函數(shù)。 // 如果傳入了 hasher,則用 hasher 函數(shù)來計(jì)算 key // 否則用 參數(shù) key(即 memoize 方法傳入的第一個參數(shù))當(dāng) key var address = "" + (hasher ? hasher.apply(this, arguments) : key); // 如果 key 還沒有對應(yīng)的 hash 值(意味著沒有緩存值,沒有計(jì)算過該輸入) // 就執(zhí)行回調(diào)函數(shù),并緩存結(jié)果值 if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); // 從緩存對象中取結(jié)果值 return cache[address]; }; // cache 對象被當(dāng)做 key-value 鍵值對緩存中間運(yùn)算結(jié)果 memoize.cache = {}; // 返回 momoize 函數(shù), 由于返回函數(shù)內(nèi)部引用了 memoize.cache, 構(gòu)成了閉包,變量保存在了內(nèi)存中。 return memoize; };三、應(yīng)用 - 判斷素?cái)?shù)
質(zhì)數(shù)為在大于 1 的自然數(shù)中,除了 1 和它本身以外不再有其他因數(shù)。
我們通過判斷素?cái)?shù)的函數(shù),看看使用了函數(shù)記憶后的效果。
function isPrime(value) { console.log("isPrime 被執(zhí)行了!"); var prime = value != 1; // 1 不是素?cái)?shù),其他數(shù)字默認(rèn)是素?cái)?shù)。 for (var i = 2; i < value; i++) { if (value % i == 0) { prime = false; break; } } return prime } let momoizedIsPrime = memoize(isPrime); momoizedIsPrime(5) // isPrime 被執(zhí)行了! momoizedIsPrime(5) // 第二次執(zhí)行,沒有打印日志!四、應(yīng)用 - 計(jì)算斐波那契數(shù)列
斐波那契數(shù)列的特點(diǎn)是后一個數(shù)等于前面兩個數(shù)的和指的是這樣一個數(shù)列:1、1、2、3、5、8、13、21、……在數(shù)學(xué)上,斐波那契數(shù)列以如下被以遞歸的方法定義:F0=0,F(xiàn)1=1,F(xiàn)n=Fn-1+Fn-2
計(jì)算斐波那契數(shù)列是用來演示函數(shù)記憶很好的例子,因?yàn)橛?jì)算斐波那契數(shù)列函數(shù)里面用了大量的遞歸。
var count = 0; var fibonacci = function(n) { count++; return n < 2 ? n : fibonacci(n - 2) + fibonacci(n - 1); } for(var i = 0; i<= 10; i++) { console.log(`i: ${i}, ` + fibonacci(i)); } // i: 0, 0 // i: 1, 1 // i: 2, 1 // i: 3, 2 // i: 4, 3 // i: 5, 5 // i: 6, 8 // i: 7, 13 // i: 8, 21 // i: 9, 34 // i: 10, 55 console.log(count); // 453 !!!
我們可以看出,如果從 0 開始打印斐波那契數(shù)列,fibonacci 函數(shù)被執(zhí)行了 453 次。那我們就犧牲一小部分內(nèi)存,用來緩存每次計(jì)算的值。
fibonacci = memoize(fibonacci); for(var i = 0; i<= 10; i++) { console.log(`i: ${i}, ` + fibonacci(i)); } console.log(count); // 12
通過 memoize 函數(shù)記憶,使得函數(shù)執(zhí)行次數(shù)只需要 12 次,大大優(yōu)化了函數(shù)執(zhí)行計(jì)算的性能消耗,
總結(jié)函數(shù)記憶(memoization)將函數(shù)的參數(shù)和結(jié)果值,保存在對象當(dāng)中,用一部分的內(nèi)存開銷來提高程序計(jì)算的性能,常用在遞歸和重復(fù)運(yùn)算較多的場景。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/101566.html
摘要:專題系列第十七篇,講解函數(shù)記憶與菲波那切數(shù)列的實(shí)現(xiàn)定義函數(shù)記憶是指將上次的計(jì)算結(jié)果緩存起來,當(dāng)下次調(diào)用時,如果遇到相同的參數(shù),就直接返回緩存中的數(shù)據(jù)。 JavaScript 專題系列第十七篇,講解函數(shù)記憶與菲波那切數(shù)列的實(shí)現(xiàn) 定義 函數(shù)記憶是指將上次的計(jì)算結(jié)果緩存起來,當(dāng)下次調(diào)用時,如果遇到相同的參數(shù),就直接返回緩存中的數(shù)據(jù)。 舉個例子: function add(a, b) { ...
摘要:源碼函數(shù)調(diào)用過,沒有變化,參數(shù)時返回緩存值。而通過,可以把上一次的計(jì)算結(jié)果保存下來,而避免重復(fù)計(jì)算。這意味著將跳過渲染組件,并重用最后渲染的結(jié)果。 1. 基本概念 在一個CPU密集型應(yīng)用中,我們可以使用Memoization來進(jìn)行優(yōu)化,其主要用于通過存儲昂貴的函數(shù)調(diào)用的結(jié)果來加速程序,并在再次發(fā)生相同的輸入時返回緩存的結(jié)果。例如一個簡單的求平方根的函數(shù): const sqrt = Ma...
摘要:將元素作為對象的鍵,默認(rèn)鍵對應(yīng)的值為如果對象中沒有這個鍵,則將這個元素放入結(jié)果數(shù)組中去。 前言 數(shù)組去重在日常開發(fā)中的使用頻率還是較高的,也是網(wǎng)上隨便一抓一大把的話題,所以,我寫這篇文章目的在于歸納和總結(jié),既然很多人都在提的數(shù)組去重,自己到底了解多少呢。又或者是如果自己在開發(fā)中遇到了去重的需求,自己能想到更好的解決方案嗎。 這次我們來理一理怎么做數(shù)組去重才能做得最合適,既要考慮兼容性,...
摘要:所有派生狀態(tài)導(dǎo)致的問題無異于兩種無條件的根據(jù)來更新無論和是否匹配來更新。派生狀態(tài)最常見的錯誤就是將這兩者混和在一起。因此通常被用于性能優(yōu)化而不是來判斷派生狀態(tài)的正確性。我們可以使用派生狀態(tài)來存儲過濾列表這種方式避免了重新計(jì)算。 原文鏈接:https://reactjs.org/blog/2018... 翻譯這篇文章的起因是因?yàn)樵谝淮涡枨蟮绣e誤的使用了getDerivedState...
閱讀 3235·2021-11-23 09:51
閱讀 2480·2021-09-27 13:34
閱讀 2464·2021-09-08 09:45
閱讀 662·2019-08-30 15:44
閱讀 3493·2019-08-29 12:17
閱讀 2759·2019-08-26 12:18
閱讀 2622·2019-08-26 10:10
閱讀 3078·2019-08-23 18:02