Leetcode 30 天 JavaScript 挑战 - 03. 函数转换

110 阅读6分钟

2629. 复合函数

请你编写一个函数,它接收一个函数数组 [f1, f2, f3,…, fn],并返回一个新的函数 fn ,它是函数数组的 复合函数 。

[f(x), g(x), h(x)] 的 复合函数 为 fn(x) = f(g(h(x))) 。

一个空函数列表的 复合函数 是 恒等函数 f(x) = x 。

你可以假设数组中的每个函数接受一个整型参数作为输入,并返回一个整型作为输出。

示例 1:

输入:functions = [x => x + 1, x => x * x, x => 2 * x], x = 4
输出:65
解释:
从右向左计算......
Starting with x = 4.
2 * (4) = 8
(8) * (8) = 64
(64) + 1 = 65

示例 2:

输出:functions = [x => 10 * x, x => 10 * x, x => 10 * x], x = 1
输入:1000
解释:
从右向左计算......
10 * (1) = 10
10 * (10) = 100
10 * (100) = 1000

示例 3:

输入:functions = [], x = 42
输出:42
解释:
空函数列表的复合函数就是恒等函数

思路

其实就是用一个工厂函数,将 functions 作为闭包挂载在工厂返回的新函数上。当执行新函数的时候,反向挨个执行 functions 里存的函数,最后的结果就是想要的答案。

代码一 (常规)

type F = (x: number) => number;

function compose(functions: F[]): F {
  return function (x) {
    for (let i = functions.length - 1; i >= 0; i--) {
      x = functions[i](x);
    }
    return x;
  }
};

/**
 * const fn = compose([x => x + 1, x => 2 * x])
 * fn(4) // 9
 */

代码二 (reverse + for...of... 遍历)

type F = (x: number) => number;

function compose(functions: F[]): F {
  return function (x) {
    for (const fun of functions.reverse()) {
      x = fun(x);
    }
    return x;
  }
};

代码三 (reduceRight API)

reductRight是 reduce的反向版,从右到左进行遍历。

developer.mozilla.org/zh-CN/docs/…

例如:

const arrays = [
  [0, 1],
  [2, 3],
  [4, 5],
];
const flattened = arrays.reduceRight((arr, num) => arr.concat(num), []);
// flattened 的值是 [4, 5, 2, 3, 0, 1]

arr 为要返回的值,num 为每次遍历的元素。

所以答案与上同理,只是每次遍历的是函数数组里的函数。

type F = (x: number) => number;

function compose(functions: F[]): F {
  return function (x) {
    return functions.reduceRight((target, fn) => fn(target), x);
  }
};

2703. 返回传递的参数的长度

请你编写一个函数 argumentsLength,返回传递给该函数的参数数量。

示例 1:

输入:args = [5]
输出:1
解释:
argumentsLength(5); // 1

只传递了一个值给函数,因此它应返回 1

示例 2:

输入:args = [{}, null, "3"]
输出:3
解释:
argumentsLength({}, null, "3"); // 3

传递了三个值给函数,因此它应返回 3

提示:

  • args 是一个有效的 JSON 数组
  • 0 <= args.length <= 100

代码

送分题,不说了...

type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };

function argumentsLength(...args: JSONValue[]): number {
  // 直接返回剩余参数长度
  return args.length;
};

/**
 * argumentsLength(1, 2, 3); // 3
 */

2666. 只允许一次函数调用

给定一个函数 fn ,它返回一个新的函数,返回的函数与原始函数完全相同,只不过它确保 fn 最多被调用一次。

  • 第一次调用返回的函数时,它应该返回与 fn 相同的结果。
  • 第一次后的每次调用,它应该返回 undefined 。

示例 1:

输入:fn = (a,b,c) => (a + b + c), calls = [[1,2,3],[2,3,6]]
输出:[{"calls":1,"value":6}]
解释:
const onceFn = once(fn);
onceFn(1, 2, 3); // 6
onceFn(2, 3, 6); // undefined, fn 没有被调用

示例 2:

输入:fn = (a,b,c) => (a * b * c), calls = [[5,7,4],[2,3,6],[4,6,8]]
输出:[{"calls":1,"value":140}]
解释:
const onceFn = once(fn);
onceFn(5, 7, 4); // 140
onceFn(2, 3, 6); // undefined, fn 没有被调用
onceFn(4, 6, 8); // undefined, fn 没有被调用

提示:

  • calls 是一个有效的 JSON 数组
  • 1 <= calls.length <= 10
  • 1 <= calls[i].length <= 100
  • 2 <= JSON.stringify(calls).length <= 1000

思路

在工厂函数返回前声明一个闭包用于判断是否已经被调用过。如果没有被调用过,记录为调用过,然后返回函数执行的结果。如果调用过,直接返回 undefined。

代码

type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type OnceFn = (...args: JSONValue[]) => JSONValue | undefined

function once(fn: Function): OnceFn {
  // 声明一个闭包,用于判断函数是否已经被调用过了
  let isCalled = false;
  return function (...args) {
    if (!isCalled) {
      isCalled = true;
      return fn(...args);
    } else {
      return undefined;
    }
  };
}

/**
 * let fn = (a,b,c) => (a + b + c)
 * let onceFn = once(fn)
 *
 * onceFn(1,2,3); // 6
 * onceFn(2,3,6); // returns undefined without calling fn
 */

2623. 记忆函数

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

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

你可以假设有 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(12);// "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 <= 10^5
  • 1 <= n <= 10
  • actions.length === values.length
  • actions[i] 为 "call" 和 "getCallCount" 中的一个
  • fnName 为 "sum", "factorial" 和 "fib" 中的一个

思路

其实还是需要一个闭包。这里声明一个哈希表做闭包,用来记录输入的参数是否曾经也被输入过。

代码

type Fn = (...params: number[]) => number

function memoize(fn: Fn): Fn {
  // map 做闭包,用于存储调用过的参数与对应值哈希表
  const map = new Map();

  return function (...args) {
    // 参数序列化,作为 key
    const key = JSON.stringify(args);
    if (map.has(key)) {
      // 哈希表中查得到,说明调用过了,直接返回哈希表里存的值
      return map.get(key);
    } else {
      const value = fn(...args);
      // 没调用过,将返回值存入哈希表
      map.set(key, value);
      return value;
    }
  }
}


/** 
 * 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 
 */