文章目录
- 前言
- 题目
- 方法1:闭包使用{}作为储存容器,通过键值对缓存结果:
- 解析
- 方法2:使用Map()作为容器
- 解析
前言
力扣题目2623. 记忆函数
语言TypeScript
如内容有不对的地方,恳请指出
提示:以下是本篇文章正文内容,下面案例可供参考
题目
请你编写一个函数,它接收另一个函数作为输入,并返回该函数的 记忆化 后的结果。
记忆函数 是一个对于相同的输入永远不会被调用两次的函数。相反,它将返回一个缓存值。
你可以假设有 3 个可能的输入函数:sum 、fib 和 factorial 。
sum 接收两个整型参数 a 和 b ,并返回 a + b 。
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
actions.length === values.length
actions[i] 为 “call” 和 “getCallCount” 中的一个
fnName 为 “sum”, “factorial” 和 “fib” 中的一个
方法1:闭包使用{}作为储存容器,通过键值对缓存结果:
const isvalid = (v) => typeof v != "undefined"
type Fn = (...params: number[]) => number
function memoize(fn: Fn): Fn{
let o = {};
return function(...args) {
const key = JSON.stringify(args)
if(isvalid(o[key])){
return o[key];
}
return (o[key] = fn(...args));
}
}
解析
首先,memoize函数返回了一个内部函数,这个内部函数形成了闭包。为什么要闭包? 原因是闭包的作用是保存了memoize函数内部的局部变量o的引用,如果不使用闭包,那么每次调用memoize函数时,都会创建一个新的空对象o,而不是持续地使用同一个对象来存储缓存的结果。
let o = {};
首先声明一个储存容器o,用来储存输入过的参数和计算后的结果。
return function(...args);
之后返回一个内部函数,接受参数…args。在JavaScript(TypeScript)中,…args这个语法允许函数接受不定数量的参数,并将它们收集到一个数组中。举个例子:
function example(...args) {
console.log(args); // 打印出传递给函数的所有参数组成的数组
}
example(1, 2, 3); // 输出 [1, 2, 3]
example('a', 'b', 'c', 'd'); // 输出 ['a', 'b', 'c', 'd']
const key = JSON.stringify(args);
在返回的内部函数中,声明一个常量key为JSON.stringify(args)
,JSON.stringify用于将函数的参数args转换为一个字符串表示,以便作为缓存的键值。为什么要这样做?这是因为对象在 JavaScript 中不能直接作为对象的键,但是字符串可以。 JSON.stringify(args)的使用举个例子:
const args = [1, 2, 3];
const key = JSON.stringify(args);
console.log(key); // 输出 "[1,2,3]"
当然,你也可以使用const key = args.join("-")
来返回一个字符串,args.join("-")
的使用举个例子:
const args = [1, 2, 3];
const key = args.join("-");
console.log(key); // 输出 "1-2-3"
无论使用哪种方式,目的就是把参数转化为字符串,从而作为键来使用。
const isvalid = (v) => typeof v != "undefined"
在代码的开头,定义了isvalid函数,他的作用是检查传入的参数 v 是否是有效的。它使用了 typeof 操作符来检查变量的类型是否为 “undefined”。如果 v 的类型不是 “undefined”,则返回 true,表示 v 是一个有效的值;否则返回 false。
if(isvalid(o[key])){
return o[key];
}
isvalid() 函数用于检查给定的值是否有效,即检查缓存中是否已经存在与当前参数匹配的结果。如果 o[key] 的值有效,则表示缓存中存在相应的结果,可以直接返回,而不用重新计算。 这种技术通常被称为“缓存”或“记忆化”,可以用来提高函数的性能,尤其是在函数需要频繁调用且计算量较大的情况下。
最后,如果缓存中没有存在与当前参数匹配的结果,这时候(o[key] = fn(...args))
将上述计算得到的结果赋值给了缓存对象 o 的一个键,并返回这个结果。
方法2:使用Map()作为容器
function memoize<F extends (...args: any[]) => any>(fn: F): (...args: Parameters<F>) => ReturnType<F> {
const mp = new Map<string, any>()
return function(...args) {
const key = args.join('-')
if (mp.has(key)) {
return mp.get(key);
}
const result = fn(...args);
mp.set(key, result);
return result;
}
}
Map 是 JavaScript 中的一种数据结构,用于存储键值对的集合。它是一种有序的集合,其中的每个元素都是一个键值对。Map 对象可以用来存储任意类型的键和值,并且支持使用任意值(包括对象、原始值等)作为键或值。
解析
memoize<F extends (...args: any[]) => any>(fn: F): (...args: Parameters<F>) => ReturnType<F>
这个类型声明是一个泛型函数类型的声明,它接受一个参数 fn,并返回另一个函数类型。
<F extends (...args: any[]) => any>
定义了一个泛型类型参数 F,F 可以代表任意一个参数为任意数量任意类型的函数,且它的返回值也可以是任意类型。
(fn: F)
这部分指定了函数的参数 fn,它是类型为 F 的函数,即一个参数为任意数量任意类型的函数,且返回值可以是任意类型。
Parameters<F>
是 TypeScript 中的一个内置类型工具,用于获取函数类型 F 的参数类型组成的元组。举个例子:
function exampleFunc(a: number, b: string): boolean {
return true;
}
type ParamsType = Parameters<typeof exampleFunc>;
// ParamsType 的类型为 [number, string]
ReturnType<F>
同样是 TypeScript 中的另一个内置类型工具,用于获取函数类型 F 的返回值类型。举个例子:
function exampleFunc(a: number, b: string): boolean {
return true;
}
type ReturnTypeOfExampleFunc = ReturnType<typeof exampleFunc>;
// ReturnTypeOfExampleFunc 的类型为 boolean
(...args: Parameters<F>) => ReturnType<F>:
这部分是函数的返回类型,它表示一个新的函数类型。这个函数接受的参数与 F 函数的参数相同,但返回值类型与 F 函数的返回值类型相同。
const mp = new Map<string, any>()
创建了一个新的 Map 对象,该对象用于存储键为字符串类型、值为任意类型的键值对。
return function(...args) {
const key = args.join('-');
同理,返回内部函数。声明key,同样将args用join(’-’)转化为字符串。
if (mp.has(key)) {
return mp.get(key);
}
Map 对象有一个内置的方法叫做 has(key),用于检查 Map 中是否存在指定的键 key。这个方法会返回一个布尔值,表示是否存在对应的键值对。如果 Map 中存在指定的键 key,map.has(key) 将会返回 true;如果不存在,则返回 false。当返回结果为true的时候,返回mp.get(key)
。
Map 对象有一个内置的方法叫做 get(key),用于获取指定键 key 对应的值。如果 Map 中存在指定的键,则返回与之相关联的值;如果不存在,则返回 undefined。
if 条件成立,执行了 return 语句,函数会在这里终止,不再执行后面的代码。但是如果 if 条件不成立,函数会继续执行后面的代码。
const result = fn(...args);
mp.set(key, result);
return result;
if不成立时,执行以上代码。
set() 是 Map 对象的一个内置方法,用于向 Map 中添加或更新键值对。当你调用 set(key, value) 方法时,它会将指定的键值对添加到 Map 对象中。如果 Map 中已经存在相同的键,则会更新对应的值为新的值。如果 Map 中不存在相同的键,则会添加新的键值对。mp.set(key, result) 将计算的结果存储在了 Map 对象 mp 中,键为 key,值为 result。
最后,return result 语句将计算的结果返回给了调用者。
值得注意的是,不能将上述代码写成如下:
return mp.set(key, fn(...args))
原因是这个表达式的返回值是 Map 对象自身,而不是函数 fn 的返回值。举个例子:
const mp = new Map();
mp.set("key1", "value1");
mp.set("key2", "value2");
console.log(mp);
//Map(2) { 'key1' => 'value1', 'key2' => 'value2' }