• 记忆函数的实战应用


    力扣2623.记忆函数#

    今天在力扣做了一道题:使用JavaScript实现记忆函数,所谓记忆函数就是一个对于相同的输入永远不会被调用两次的函数。相反,它将返回一个缓存值

    以下是使用哈希表实现的方法:

    /**
     * @param {Function} fn
     * @return {Function}
     */
    function memoize(fn) {
        const map = new Map(); 
        return function(...args) {
            const item = args.join(',')
            if(!map.has(item)) {
                map.set(item, fn(...args))
            }
            return map.get(item)
        }
    }
    
    
    /** 
     * let callCount = 0;
     * const memoizedFn = memoize(function (a, b) {
     *	 callCount += 1;
     *   return a + b;
     * })
     * memoizedFn(2, 3) // 5
     * memoizedFn(2, 3) // 5
     * console.log(callCount) // 1 
     */
    

    需要说明的是,记忆函数只对纯函数(Pure function)有效,也就是对那些给定相同的输入,始终返回相同的输出,并且没有任何副作用的函数

    假如忽略这一点,可能会导致使用具有副作用的函数,会执行相应的过程,但每次后续调用都不会再得到新的结果。

    Web开发中的记忆化#

    记忆化作为一种重要的思想,在Web开发中有很多实战:

    缓存网站文件#

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

    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 发生变化时才会重新渲染,从而提高了应用程序的性能。

    缓存 API 调用#

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

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

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

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

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

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

    算法中的记忆化#

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

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

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

    但是,通过不再使用相同的输入两次调用 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 没有此问题)。

  • 相关阅读:
    【Kafka】10道不得不会的 Kafka 面试题
    C# 支付宝小程序 ---小程序支付
    面霸的自我修养:Java线程专题
    如何在前端开发中实现摄像头拍照和人像定位
    电压频率的变换原理
    【前端】搭建 Vite + P5.js 项目
    SpringBoot异常统一处理,包括系统异常、自定义异常和参数检验异常
    Spring Boot 优雅配置yml配置文件定义集合、数组和Map
    Hadoop源码编译打包
    什么是离岸金融 (OFFSHORE FINANCE)
  • 原文地址:https://www.cnblogs.com/gfhcg/p/17977177