当前位置: 首页 > news >正文

网站重要三要素三门峡做网站推广

网站重要三要素,三门峡做网站推广,江门网站制作开发,深圳搜索竞价账户托管【力扣】2623. 记忆函数——函数转换 文章目录【力扣】2623. 记忆函数——函数转换一、题目二、解决方案1、概述1.1纯函数2、在Web开发中的记忆化用途2.1缓存网站文件(1)React 组件(2)缓存 API 调用3、算法中的记忆化4、专业实现的…

【力扣】2623. 记忆函数——函数转换

文章目录

  • 【力扣】2623. 记忆函数——函数转换
    • 一、题目
    • 二、解决方案
      • 1、概述
        • 1.1纯函数
      • 2、在Web开发中的记忆化用途
        • 2.1缓存网站文件
          • (1)React 组件
          • (2)缓存 API 调用
      • 3、算法中的记忆化
      • 4、专业实现的考虑
        • 4.1处理任意输入
        • 4.2内存管理
      • 方法 1:使用 Rest/Spread 语法 + JSON.stringify()
      • 方法 2:使用参数语法
      • 方法 3:基于数字约束进行优化 + Function.apply
      • 方法4:一行代码
      • 5、复杂度分析

一、题目

请你编写一个函数 fn,它接收另一个函数作为输入,并返回该函数的 记忆化 后的结果。

记忆函数 是一个对于相同的输入永远不会被调用两次的函数。相反,它将返回一个缓存值。

你可以假设有 3 个可能的输入函数:sumfibfactorial

  • sum 接收两个整型参数 ab ,并返回 a + b 。假设如果参数 (b, a) 已经缓存了值,其中 a != b,它不能用于参数 (a, b)。例如,如果参数是 (3, 2)(2, 3),则应进行两个单独的调用。
  • fib 接收一个整型参数 n ,如果 n <= 1 则返回 1,否则返回 fib (n - 1) + fib (n - 2)
  • factorial 接收一个整型参数 n ,如果 n <= 1 则返回 1 ,否则返回 factorial(n - 1) * n

示例 1:

输入:
fnName = "sum"
actions = ["call","call","getCallCount","call","getCallCount"]
values = [[2,2],[2,2],[],[1,2],[]]
输出:[4,4,1,3,2]
解释:
const sum = (a, b) => a + b;
const memoizedSum = memoize(sum);
memoizedSum (2, 2);// "call" - 返回 4。sum() 被调用,因为之前没有使用参数 (2, 2) 调用过。
memoizedSum (2, 2);// "call" - 返回 4。没有调用 sum(),因为前面有相同的输入。
// "getCallCount" - 总调用数: 1
memoizedSum(1, 2);// "call" - 返回 3。sum() 被调用,因为之前没有使用参数 (1, 2) 调用过。
// "getCallCount" - 总调用数: 2

示例 2:

输入:
fnName = "factorial"
actions = ["call","call","call","getCallCount","call","getCallCount"]
values = [[2],[3],[2],[],[3],[]]
输出:[2,6,2,2,6,2]
解释:
const factorial = (n) => (n <= 1) ? 1 : (n * factorial(n - 1));
const memoFactorial = memoize(factorial);
memoFactorial(2); // "call" - 返回 2。
memoFactorial(3); // "call" - 返回 6。
memoFactorial(2); // "call" - 返回 2。 没有调用 factorial(),因为前面有相同的输入。
// "getCallCount" -  总调用数:2
memoFactorial(3); // "call" - 返回 6。 没有调用 factorial(),因为前面有相同的输入。
// "getCallCount" -  总调用数:2

示例 3:

输入:
fnName = "fib"
actions = ["call","getCallCount"]
values = [[5],[]]
输出:[8,1]
解释:
fib(5) = 8 // "call"
// "getCallCount" - 总调用数:1

提示:

  • 0 <= a, b <= 105
  • 1 <= n <= 10
  • 1 <= actions.length <= 105
  • actions.length === values.length
  • actions[i] 为 “call” 和 “getCallCount” 中的一个
  • fnName 为 “sum”, “factorial” 和 “fib” 中的一个

二、解决方案

1、概述

此问题要求你编写一个修改所提供函数的函数,使所提供的函数只有在没有传递参数的情况下才会被调用。如果之前已传递过这些参数,它应返回之前的输出而且无需调用所提供的函数。这种类型的优化称为 记忆化,是 高阶函数 的一个极其重要的示例。

为了具体说明记忆化,以下是没有记忆化的一些代码示例。

let callCount = 0;
const add = (a, b) => {callCount += 1;return a + b;
}add(2, 2); // 4
console.log(callCount); // 1
add(2, 2); // 4
console.log(callCount); // 2
add(2, 2); // 4
console.log(callCount); // 3

不出所料,每次调用add时都会增加 callCount

然而,如果我们应用 记忆化

let callCount = 0;
const add = (a, b) => {callCount += 1;return a + b;
};
const memoizedAdd = memoize(add);memoizedAdd(2, 2); // 4
console.log(callCount); // 1
memoizedAdd(2, 2); // 4
console.log(callCount); // 1
memoizedAdd(2, 2); // 4
console.log(callCount); // 1

就可以看到callCount仅在第一次调用memoizedAdd时增加。每次后续传递 (2, 2) 时,记忆化逻辑会检测到这些参数以前已传递,并立即返回缓存的值 4,而不调用 add

避免添加两个数字显然算不上什么巨大的优化,但可以想象如果是对一个复杂的多的函数进行记忆化,会给性能带来多大的提升。

1.1纯函数

值得注意的是,记忆化 仅对 纯函数 有效。纯函数的定义为:给定相同的输入,始终返回相同的输出,并且没有任何副作用的函数。

例如,假设你尝试记忆化不纯的函数 Date.now,它返回自Unix时间戳以来的当前时间(以毫秒为单位)。

const getCurrentTimeMemoized = memoize(Date.now);getCurrentTimeMemoized(); // 1683784131157
getCurrentTimeMemoized(); // 1683784131157
getCurrentTimeMemoized(); // 1683784131157

getCurrentTimeMemoized 第一次调用时会正确返回当前时间。但每次后续调用时,它都会错误地返回和第一次相同的值。

类似的,假设你有一个具有副作用的函数,如将数据上传到数据库。

function uploadRow(row) {// 上传逻辑
}const memoizedUpload = memoize(uploadRows);
memoizedUpload('Some Data'); // 成功上传
memoizedUpload('Some Data'); // 什么都不会发生

第一次调用memoizedUpload时,数据将正确上传到数据库,但每次后续调用都不会再得到新的结果。

事实上,你只能在纯函数上应用此优化,这也是尽可能使函数纯粹的一个很好的理由。

2、在Web开发中的记忆化用途

记忆化有无数的用途,在这里我们讨论其中一些典型案例。

2.1缓存网站文件

大型网站通常由许多 JavaScript 文件组成,在用户访问不同页面时会动态下载这些文件。有时会采用一种模式,其中文件名基于文件内容的哈希值。这样,当 Web 浏览器请求已经在之前请求过的文件名时,它可以从磁盘上本地加载文件,而不必重新下载它。

(1)React 组件

React 是一个非常流行的用于构建用户界面的库,尤其适用于单页面应用程序。其核心原则之一是将应用程序分解为单独的 组件。每个组件负责渲染应用程序HTML的不同部分。

例如,你可能有一个组件如下:

const TitleComponent = (props) => {return <h1>{props.title}</h1>;
};

上面的函数将在每次父组件渲染时调用,即使title没有更改。通过在其上调用 React.memo,可以提高性能,避免不必要的渲染。

const TitleComponent = React.memo((props) => {return <h1>{props.title}</h1>;
});

现在,TitleComponent 只有在title发生变化时才会重新渲染,从而提高了应用程序的性能。

(2)缓存 API 调用

假设你有一个函数,用于向API发送网络请求以访问数据库中的键值对。

async function getValue(key) {// 数据库请求逻辑
}
const getValueMemoized = memoize(getValue);

现在,getValueMemoized 将仅为每个键进行一次网络请求,可能大大提高性能。需要注意的是,由于getValue是异步的,它将返回一个 Promise 而不是实际值。对于这种用例,这实际上是最理想的,因为即使在第一次请求返回值之前调用两次,它仍然只会进行一次网络请求。

记忆化网络请求的一个潜在缺点是数据陈旧的风险。如果数据库中与特定键关联的值发生更改,记忆化函数可能仍然返回旧的缓存结果,使用户无法看到更新。

处理这种情况的几种方法:

  1. 始终向API发送请求,询问值是否已更改。
  2. 使用WebSocket订阅数据库中值的更改。
  3. 为值提供 过期时间,以使用户至少不会看到太过时的数据。

你可以在 此处 了解有关 HTTP 缓存的更多信息。

3、算法中的记忆化

记忆化的一个经典应用是在 动态规划 中,将问题分解为若干子问题。这些子问题可以表示为函数调用,其中许多函数调用多次且使用相同的输入,因此可以进行优化。

动态规划极大提高效率的一个经典示例是计算斐波那契数。

function fib(n) {if (n <= 1) return n;return fib(n - 1) + fib(n - 2);
}
fib(100); // 耗时多年

上面的代码非常低效,时间复杂度为 O(1.6 n )(1.6是黄金比率)。

但是,通过不再使用相同的输入两次调用 fib,我们可以在 O(n) 的时间内计算斐波那契数。

const cache = new Map();
function fib(n) {if (n <= 1) return n;if (cache.has(n)) {return cache.get(n);}const result = fib(n - 1) + fib(n - 2);cache.set(n, result);return result;
}
fib(100); // 几乎立即解决

我们是否可以只是调用了fib的第一个实现,然后在其上写了memoizedFib = memoize(fib);以获得相同的性能优化?不幸的是,不能。fib 的原始实现引用了自身(未记忆化版本)。因此,如果调用 memoizedFib(100),缓存只会添加一个键(100),仍然需要数年时间才能计算。这是 JavaScript 的一个基本限制(Python 没有此问题)。

4、专业实现的考虑

4.1处理任意输入

之所以仅假设将 3 个具体的函数作为参数传递,都具有数值输入,是有原因的。这是因为数字具有唯一的字符串表示,使缓存逻辑更简单。如果函数可以传递任意输入,你将需要比仅将输入直接转换为字符串更复杂的方法。考虑解决 2630. 记忆函数 II,它需要更通用的方法。

4.2内存管理

由于你可以无限次地调用函数并传递不同的输入,因此可能会耗尽内存。实施一些机制来限制缓存大小非常重要。一种方法是使用Least Recently Used(LRU)缓存。你可以在 2630. 记忆函数 II 的底部相关信息。

方法 1:使用 Rest/Spread 语法 + JSON.stringify()

在 JavaScript 中,你可以使用rest语法访问所有参数作为数组。然后,可以将参数数组展开以将其传递回函数。

由于参数是数字数组(即有效的 JSON),将它们转换为字符串键的便捷方式是使用 JSON.stringify()

  1. 初始化一个用于新的记忆化函数的缓存对象。
  2. 每次调用记忆化函数时,将传递的参数转换为字符串。
  3. 检查键是否已存在于缓存中。如果是,则立即返回关联的值。
  4. 否则,调用提供的函数,将输出放入缓存,并返回输出。
function memoize(fn) {const cache = {};return function(...args) {const key = JSON.stringify(args);if (key in cache) {return cache[key];}const functionOutput = fn(...args);cache[key] = functionOutput;return functionOutput;}
}

方法 2:使用参数语法

JavaScript 还允许你使用特殊的arguments变量访问传递的参数。

使用arguments变量时需要注意以下几点:

  1. 它不能与箭头函数一起使用,而是引用任何包含非箭头函数的封闭函数。
  2. 虽然arguments类似于数组,但实际上是一个类似 数组的可迭代 对象。像循环遍历它和访问索引一样的操作会按预期工作。但是,调用pushjoin等方法不会起作用。
  3. 在处理可变参数时,通常最佳实践是使用rest语法,而arguments主要用于较旧的代码库。
function memoize(fn) {const cache = {};return function() {// 将参数转换为字符串let key = '';for (const arg of arguments) {key += ',' + arg;}if (key in cache) {return cache[key];}const functionOutput = fn(...arguments);cache[key] = functionOutput;return functionOutput;}
}

方法 3:基于数字约束进行优化 + Function.apply

将参数转换为字符串是一个相对昂贵的操作。因为根据问题的约束,永远不会有超过两个参数,参数永远不会大于 100,000,所以我们可以避免将它们转换为字符串。

假设你有两个数字a b,并且希望将它们转换为一个唯一的数字,使得没有其他值的ab映射到相同的数字。你可以使用公式 key = a + (b * 100001)

我们还可以使用Function.apply方法调用提供的函数。它的第一个参数是this上下文,我们可以将其设置为 null,因为提供的函数不引用 this。第二个参数是要传递给函数的参数。

function memoize(fn) {const cache = new Map();return function() {let key = arguments[0];if (arguments[1]) {key += arguments[1] * 100001;}const result = cache.get(key);if (result !== undefined) {return result;}const functionOutput = fn.apply(null, arguments);cache.set(key, functionOutput);return functionOutput;}
}

方法4:一行代码

为了展示 JavaScript 提供的一些语法,以下是一种一行代码的解决方案。让我们看看代码的不同部分,以了解它是如何工作的。

  1. var memoize = (fn, cache = {}) => (...args) => 定义了 memoize函数,它接受两个参数:一个函数 fn 和一个可选的缓存对象 cache。由于永远不会传递第二个参数,cache将始终设置为一个空对象 {}memoize 函数继续返回另一个函数,它接受任意数量的参数。
  2. ?? 这是Nullish合并运算符。仅当左侧的第一个操作数不为nullundefined时,它才会返回左侧的第一个操作数。否则,它将返回右侧的第二个操作数。
  3. cache[args.join()] 将参数转换为逗号分隔的字符串,并返回与该键关联的值。如果值不存在,则返回 undefined(导致函数返回右侧的值)。
  4. (cache[args.join()] = fn(...args)) 将缓存中的键设置为提供的函数的输出。然后返回该值。如果存在缓存未命中,将执行此代码。
var memoize = (fn, cache = {}) => (...args) => cache[args.join()] ?? (cache[args.join()] = fn(...args))

5、复杂度分析

以下分析适用于所有方法。

设 N 为以前调用函数的次数。

  • 时间复杂度:O(1)。每次调用记忆化函数时,只执行一次字典查找。
  • 空间复杂度:O(N)。在最坏的情况下,需要存储之前的所有参数。
http://www.dtcms.com/a/542733.html

相关文章:

  • Unity UGC IDE实现深度解析(二):端口系统与类型安全机制
  • 怎么建设自己网站首页网络开发理论
  • 网站建设可实施性报告众筹网站搭建
  • 做网站和游戏是如何赚钱网上国网推广方案
  • 一个专门做特产的网站自己制作免费网页
  • 网站域名使用怎么做待摊分录做网站书
  • 网站设计的一般步骤是什么?做网站子页
  • 延吉市住房城乡建设局官方网站域名是干嘛的
  • 抖音点赞自助网站什么是优化网站
  • FreeRTOS队列实战:血氧监测系统设计
  • vscode使用verilog format插件教程
  • 锒川市住房和城乡建设局网站公告设计师培训基地
  • 在哪个网站做科目一考试题网站建设后如何修改
  • Linux Shell SSH命令
  • 哪个网站教做衣服深圳建设交易工程信息网
  • 无忧网站建设哪家好长沙整合推广
  • 沈阳好的网站网络营销工作岗位有哪些
  • 公司网站怎么写上海高端网站建设公
  • 南京网站设计是什么包装设计网站是什么样子的
  • 电子商务网站开发技术支持南京紫米网络科技有限公司
  • 响应式商业网站开发实训报告快速建站介绍
  • 2008iis网站属性网页设计报告2000字
  • QGraphicsEffect控件添加特效
  • 80s无水印视频素材网站下载无水印logo免费一键生成
  • 维护网站建设空间出租搜索引擎营销怎么做
  • 做版权保护的网站湖南网站建设制作
  • 广州门户网站建设wordpress改登录界面
  • 东莞市建设局网站公司网站建设注册
  • 网站备案怎么办网络空间安全考研
  • 通州网站建设是什么wordpress 4.7.2 被黑