力扣前端leetcode 2623. 记忆函数语言TypeScript(详细分析)TS

文章目录

  • 前言
  • 题目
  • 方法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' }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值