leetcode — JavaScript专题(八):间隔取消、使用方法链的计算器、判断对象是否为空、记忆函数 II、设计可取消函数

专栏声明:只求用最简单的,容易理解的方法通过,不求优化,不喜勿喷

2725. 间隔取消

题面

现给定一个函数 fn,一个参数数组 args 和一个时间间隔 t,返回一个取消函数 cancelFn。函数 fn 应该立即使用 args 调用,并且在每个 t 毫秒内再次调用,直到调用 cancelFn。

知识点:

定时器、闭包

思路

这题主要考察定时器的使用,它的计数器的调用类似,区别是:

  • 定时器是指一定间隔后再次调用其中的逻辑,循环调用直到我们清除它
  • 计时器是在一定延迟后调用一次其中的逻辑,之后自动结束
    这题的逻辑显然是让我们使用定时器来实现,要注意的是,定时器不会马上执行一次,所以我们需要在定时器开始之前先执行一次这个逻辑。之后我们再返回一个取消这个定时器的函数即可,其原理是闭包我们就不再多说了
代码
/**
 * @param {Function} fn
 * @param {Array} args
 * @param {number} t
 * @return {Function}
 */
var cancellable = function(fn, args, t) {
    fn(...args);
    let timer = setInterval(() => {
        fn(...args)
    },t);
    return cancelFn = () => {
        clearInterval(timer);
    }
    
};

/**
 *  const result = []
 *
 *  const fn = (x) => x * 2
 *  const args = [4], t = 20, cancelT = 110
 *
 *  const log = (...argsArr) => {
 *      result.push(fn(...argsArr))
 *  }
 *       
 *  const cancel = cancellable(fn, args, t);
 *           
 *  setTimeout(() => {
 *     cancel()
 *     console.log(result) // [
 *                         //      {"time":0,"returned":8},
 *                         //      {"time":20,"returned":8},
 *                         //      {"time":40,"returned":8},           
 *                         //      {"time":60,"returned":8},
 *                         //      {"time":80,"returned":8},
 *                         //      {"time":100,"returned":8}
 *                         //  ]
 *  }, cancelT)
 */

2726. 使用方法链的计算器

题面

设计一个类 Calculator 。该类应提供加法、减法、乘法、除法和乘方等数学运算功能。同时,它还应支持连续操作的方法链式调用。Calculator 类的构造函数应接受一个数字作为 result 的初始值。
你的 Calculator 类应包含以下方法:
add - 将给定的数字 value 与 result 相加,并返回更新后的 Calculator 对象。
subtract - 从 result 中减去给定的数字 value ,并返回更新后的 Calculator 对象。
multiply - 将 result 乘以给定的数字 value ,并返回更新后的 Calculator 对象。
divide - 将 result 除以给定的数字 value ,并返回更新后的 Calculator 对象。如果传入的值为 0 ,则抛出错误 “Division by zero is not allowed” 。
power - 将 result 的值乘以给定的数字 value ,并返回更新后的 Calculator 对象。
getResult - 返回 result 的值。
结果与实际结果相差在 10-5 范围内的解被认为是正确的。

知识点:

模拟,throw

思路

一个含简单的模拟题,初始化的时候将 class 的 value 值设置为传入的值,之后的操作都是对这个 value 进行操作,加法减法乘法直接模拟即可,pow 操作调用 Math 库的 api 或者直接模拟,触发需要判定一下,如果是除 0 操作我们需要 throw 一个错误。

代码
class Calculator {
  
  /** 
   * @param {number} value
   */
  constructor(value) {
      this.value = value
  }

  /** 
   * @param {number} value
   * @return {Calculator}
   */
  add(value){
    this.value += value
  }

  /** 
   * @param {number} value
   * @return {Calculator}
   */
  subtract(value){
      this.value -= value
  }

  /** 
   * @param {number} value
   * @return {Calculator}
   */  
  multiply(value) {
    this.value *= value
  }

  /** 
   * @param {number} value
   * @return {Calculator}
   */
  divide(value) {
      if(value == 0){
          throw "Division by zero is not allowed"
      }
       this.value /= value
  }
  
  /** 
   * @param {number} value
   * @return {Calculator}
   */
  power(value) {
        this.value = Math.pow( this.value,value )    
  }
    
  /** 
   * @return {number}
   */
  getResult() {
      return this.value
  }
}

2727. 判断对象是否为空

题面

给定一个对象或数组,判断它是否为空。
一个空对象不包含任何键值对。
一个空数组不包含任何元素。
你可以假设对象或数组是通过 JSON.parse 解析得到的。

知识点:

判空

思路

简单的分类讨论,如果是数组,判断其长度,如果是对象,判断其 key 值数组的长度即可

代码
/**
 * @param {Object | Array} obj
 * @return {boolean}
 */
var isEmpty = function(obj) {
    if(!Array.isArray(obj)){
       return Object.keys(obj).length == 0;
    }else{
        return obj.length == 0;
    } 
};

2630. 记忆函数 II

题面

之前还有两个困难题目我们来补一下

现给定一个函数 fn ,返回该函数的一个 记忆化 版本。

一个 记忆化 的函数是一个函数,它不会被相同的输入调用两次。而是会返回一个缓存的值。

函数 fn 可以是任何函数,对它所接受的值类型没有任何限制。如果两个输入值在 JavaScript 中使用 === 运算符比较时相等,则它们被视为相同。

知识点:

哈希表

思路

这题和 2623. 记忆函数 的区别是传入的内容可以是任何值,也就是说我们简单的使用 toString 获得的内容不一定是可以标识参数的,那么此时我们需要转换一下思路,我们还是按照 2623. 记忆函数 的写法来写这题,那么怎么样我们的参数才能按照原先的逻辑处理呢,那么就是我们再用一个哈希表,把传入的参数映射成一个 int 类型,这样我们再用 join(“-”) 串起整个处理后的参数列表就可以唯一标识一组参数了,下面是具体实现:

  • 先用一个哈希表来记录每个参数,以参数作为 key,递增的 int 类型变量 now 作为 value。如果传入的某一个参数出现过,返回其映射成的 int 值,否则在哈希表中新增一项,now 自增。
  • 之后把处理完的参数列表 join(“-”) 成一个字符串,再创建一个哈希表,以这个字符串为key,函数的处理结果为 value,如果对应的字符串出现过,直接返回缓存的运行结果,否则用这些参数运行我们的函数,用哈希表缓存它。
代码
/**
 * @param {Function} fn
 */
function memoize(fn) {

    const map = new Map();
    const hash = new Map();
    let now = 0;
    return function (...args) {
        let kk = [];
        for (var i = 0; i < args.length; i++) {
            if(!hash.has(args[i])){
                hash.set(args[i], now++);
            }
            kk.push(hash.get(args[i]))
        }
        let key = kk.join("-");
        if (!map.has(key)) map.set(key, fn.apply(null, args));
        return map.get(key);
    }
}

2650. 设计可取消函数

题面

有时候你会有一个长时间运行的任务,并且你可能希望在它完成之前取消它。为了实现这个目标,请你编写一个名为 cancellable 的函数,它接收一个生成器对象,并返回一个包含两个值的数组:一个 取消函数 和一个 promise 对象。

你可以假设生成器函数只会生成 promise 对象。你的函数负责将 promise 对象解析的值传回生成器。如果 promise 被拒绝,你的函数应将该错误抛回给生成器。

如果在生成器完成之前调用了取消回调函数,则你的函数应该将错误抛回给生成器。该错误应该是字符串 “Cancelled”(而不是一个 Error 对象)。如果错误被捕获,则返回的 promise 应该解析为下一个生成或返回的值。否则,promise 应该被拒绝并抛出该错误。不应执行任何其他代码。

当生成器完成时,您的函数返回的 promise 应该解析为生成器返回的值。但是,如果生成器抛出错误,则返回的 promise 应该拒绝并抛出该错误。

知识点:

生成器、Promise

思路

这是目前未知逻辑最复杂的题目,很多人可能连题目都读不懂,我们先来解读一下题目:

  • 传入一个生成器其中的每一步都是一个 promise 对象,你需要返回一个 promise 对象和一个取消函数,如果正常运行,你的生成器会自动执行直到运行完毕,然后通过 promise 的成功函数返回值。如果你中途取消了这个它,他会通过成功函数返回值 “Cancelled”,如果运行中出现了错误,他会捕获这个错误,然后返回失败函数和错误信息。
  • 那么现在我们先来搭建框架,首先我们把需要返回的 promise 定为 p,函数定为 cancel ,之后我们在 promise 中定义一个 run 函数作为我们执行的入口。
  • 我们先看正常执行的情况,此时我们需要通过生成器的 next 函数执行下一步
  • 之后我们判定执行的返回值中的 done 标志位,如果 done 是 true 说明运行完毕,直接通过 promise 的成功函数返回结果即可。
  • 如果 done 是 false ,我们需要继续执行,因为生成器中都是 promise 对象,我们需要用 then 函数等待它接收完毕,如果正常运行结束则再次调用 run 函数继续下一步
  • 如果运行中有错误了,我们需要把 isError 标志位设为 true,然后继续下一次的迭代
  • 我们继续来讲我们的逻辑,因为即使有错误了,错误也会是 promise,所以这个标志位仅仅是让我判定是用 next 还是用 throw 进行处理,下一次迭代中,如果 isError 标志位是 true,那么我们就需要换用 throw 而不是 next 进行处理。
  • 最后来讲我们的取消,当我们取消的时候,我们需要把 isError 和 isCancel 都标记为 true,当我们按照 isError 处理完毕后,isCancel 为true 让我们直接返回,而不进行下一步的运行
  • 最后如果有不是迭代器内部抛出的错误,我们需要捕获它,然后通过 promise 函数的失败函数返回错误信息。
代码
/**
 * @param {Generator} generator
 * @return {[Function, Promise]}
 */
var cancellable = function (generator) {

    let isCancel = false;  //是不是取消
    let isError = false;   //是不是错误
    let cancel = null;     // 取消函数
    let p = new Promise((res, rej) => {
        cancel = () => {
            isError = true;
            isCancel = true;
            run("Cancelled")
            return
        };
        let run = (val) => {
            try {
                let re = null;
                if (isError) {
                    re = generator.throw(val);
                    // 被取消
                    if (isCancel) {
                        res(re.value);
                        return;
                    }else{
                        isError = false;
                    }
                } else {
                    // 正常执行
                    re = generator.next(val);
                }
                if (re.done) {
                    res(re.value);
                    return;
                } else {
                    re.value.then((e) => {
                        run(e);
                    }).catch((e) => {
                        isError = true;
                        run(e);
                    })
                }
            } catch (e) {
                rej(e);
            }
        }
        run(null);
    })
    return [cancel, p]
};

/**
 * function* tasks() {
 *   const val = yield new Promise(resolve => resolve(2 + 2));
 *   yield new Promise(resolve => setTimeout(resolve, 100));
 *   return val + 1;
 * }
 * const [cancel, promise] = cancellable(tasks());
 * setTimeout(cancel, 50);
 * promise.catch(console.log); // logs "Cancelled" at t=50ms
 */

你可能感兴趣的:(leetcode,—,javascript专题,javascript,leetcode,开发语言,前端,typescript)