【问题标题】:Memoize function passes function and returns function JavaScriptMemoize 函数传递函数并返回函数 JavaScript
【发布时间】:2016-10-30 11:45:16
【问题描述】:

我在使用此功能时遇到了多个问题。这是一门数据结构和算法课程的额外问题的一部分,我在这个问题上投入了很多时间,我真的很想让它工作并了解正在发生的事情。

有一个主要问题,导致了几个小问题……这个问题的名称是 JavaScript。我们以前从未使用过 JavaScript 编程,但出于某种原因,我们不得不使用它。

函数必须通过测试(这个和斐波那契),其结构如下:

var fn = (n) => 2 * n
var m_fn = memoize(fn)
expect(m_fn(18)).to.equal(fn(18))

所以我必须将我想要记忆的函数作为记忆函数的参数传递,记忆函数必须返回一个函数。我不允许以任何其他方式这样做。

我阅读并研究了 memoize 函数,但所有的实现都采用了不同的方法。

基本上,我了解我必须做什么,但我不太了解如何。我知道 memoize 函数应该做什么,但我不明白如何使用 memoize 函数调整原始函数。这是我目前拥有的/我没有的:

我知道这是错误的。但我想我错过了一些重要的东西。我应该返回一个函数,但我正在返回值...

在测试中,写的是 var m_fn = memoize(fn),所以 memoize 函数通过 fn,然后返回一个新函数,但是在我的 memoize 中,我正在返回 fn(n) 的值,所以我正在做一些事情错了……

/**
* Creates a memoized version of the function fn. It is assumed that fn is a referentially transparent
* function.
* @param {function} fn Some referentially transparent function that takes a basic datatype (i.e. number / string)
* @returns {function} A new function that is the memoized version of fn. It never calculates the result of
* a function twice.
*/
memoize: (fn) => { //here we enter the function that we want to memoize
 var memory = []; //we need to create an array to hold the previously calculated values, length n (parameter of fn)

 if(this.n > memory.length){ //Check to see if this particular value is in the array already.  
   return memory[this.n]; //How do I access the integer parameter that was passed through fn though? Is this correct?
 } else{ // if not, we want to save it and return it
   var result = fn(this.n);
   memory.push(result);
   return result;
 } 

}

【问题讨论】:

  • 我认为 OP 使用 “问题的名称是 JavaScript” 作为夸张,并且非常清楚他们缺乏理解。
  • 在代码注释中你说过 “一个新函数,它是 fn 的记忆版本。它永远不会计算函数的结果两次。”@987654323 也是如此@ 应该缓存给定输入值调用fn 的结果并直接返回结果而不是再次调用它?可以对fn(以及因此m_fn)将收到的参数做出哪些假设(如果有)?
  • 是的,我很清楚我对 JavaScript 一无所知。上周我确实尝试过掌握基础知识,但正如我所提到的,我们从未真正有过“JavaScript 入门”;我们只是被告知要在其中编程。虽然这仍然是一个借口,但这也是我难以理解这一点的很大一部分原因。
  • fn 是否可以是另一个需要多个参数和/或非数字参数的函数,例如对象?或者我们应该假设fn 将始终是一个接受一个数字参数的函数?
  • memoize 函数是一个旨在通过将先前计算的递归值保存在数组或类似数据结构中来减少递归必要性的函数。因此,如果该函数计算出 4 的斐波那契值,则当它调用第一个递归 fib(n-1) 或在此示例中。 3,它将保存该递归的fib值(fib(2)和fib(1)),这样当第二个递归在这个例子中被称为fib(n-2)或2时,它们就不必是通过递归计算,因为值保存在数组中,当我们计算 n-1 时。

标签: javascript parameters memoization


【解决方案1】:

确实,你需要返回一个函数。

其次,数组不是memory 的理想结构,因为在其中找到参数值需要线性时间。我建议为此使用Map,这是此类用途的理想选择。它有has()get()set() 方法,可以在近乎恒定的时间内运行:

function memoize(fn) {
    var memory = new Map();
    return function(arg) {
        if (memory.has(arg)) {
            console.log('using memory');
            return memory.get(arg);
        } else {
            var result = fn(arg);
            memory.set(arg, result);
            return result;
        }
    };
}

var fn = (n) => 2 * n
var m_fn = memoize(fn)

console.log(fn(18));
console.log(m_fn(18));
console.log(m_fn(18)); // outputs also "using memory"

【讨论】:

  • 刚刚意识到我们可以使用Map 在我的回答中添加评论说“我假设我们可以使用 ES2015 功能......”:-)
  • 我们想法相同 :-)
  • 是的!谢谢!这就是我所缺少的。只是为了确认我理解正确。 arg 对应于我通过原始函数传递的参数 n,fn?
  • @LisaEver:是的。当你调用m_fn时,你调用的是memoize返回的函数,它接收你给它的参数为arg,然后返回存储的结果或调用fn并存储它(并返回结果)。
  • 确实,我称它为arg 是为了默默地暗示它也可以是一个字符串(如果记忆函数可以处理)或其他原始(!)值。但确实是你说的那个n
【解决方案2】:

您可以使用Map 作为内存。

var memoize = f => 
        (map => v => (!map.has(v) && map.set(v, f(v)), map.get(v)))(new Map),
    fn = (n) => 2 * n,
    m_fn = memoize(fn);

console.log(m_fn(18), fn(18));

【讨论】:

  • 一如既往的简洁;-)
  • 我把参数改成了f
  • 对不起。欺骗我的是console.log(m_fn(18), fn(18));,我读为console.log(m_fn(18), m_fn(18));。我正疯了试图弄清楚为什么它多次调用fn以获得相同的值......
  • 我真的很喜欢这个答案,但正如@T.J.Crowder 刚刚展示的那样,这并不是最容易理解的,特别是如果 OP 发现 JavaScript 是一个 problem :(
  • 有点打高尔夫球;)
【解决方案3】:

查看您的代码和代码内 cmets 并假设我的解释正确,您真的很接近解决方案。正如您在问题中所说,您需要返回一个返回值而不是返回值的 函数

解释见cmets:

function memoize(f) {
  // An array in which to remember objects with the input arg and result 
  var memory = [];
  
  // This is the function that will use that array; this is the
  // return value of memoize
  return function(arg) {
    // This code runs when the function returned by memoize is called
    // It's *here* that we want to process the argument, check the `memory`
    // array, call `f` if necessary, etc.
    var entry;
    
    // See if we have a previously-saved result for `arg`
    var entry = memory.find(entry => entry.arg === arg);
    if (!entry) {
      // No -- call `fn`, remember the `arg` and result in an object
      // we store in memory``
      entry = {arg, result: f(arg)};
      memory.push(entry);
    }
    
    // We have it (now), return the result
    return entry.result;
  };
}
function fn(arg) {
  console.log("fn called with " + arg);
  return 2 * arg;
}
var m_fn = memoize(fn);
console.log(m_fn(18));
console.log(m_fn(18));
console.log(m_fn(20));
console.log(m_fn(20));

注意:您的代码中有一个箭头函数,所以我假设可以使用上面的 ES2015 功能。但实际上并没有太多,只是传递给memory.find 的箭头函数,Array#find 可用的假设,以及用于创建入口对象的语法(在 ES5 中我们需要entry = {arg: arg, result: f(arg)}) .

请注意,如果我们可以假设arg 将是一个字符串或数字或其他可以可靠地转换为字符串的值,我们可以使用对象而不是数组来存储数据。

实际上,鉴于这是 ES2015,我们可以使用 Map

function memoize(f) {
  // An Map in which to remember objects with the input arg and result 
  const memory = new Map();
  
  // This is the function that will use that array; this is the
  // return value of memoize
  return function(arg) {
    // This code runs when the function returned by memoize is called
    // It's *here* that we want to process the argument, check the `memory`
    // array, call `f` if necessary, etc.
    let result;
    
    // See if we have a previously-saved result for `arg`
    if (!memory.has(arg)) {
      // No -- call `fn`, remember the `arg` and result in an object
      // we store in memory``
      result = f(arg);
      memory.set(arg, result);
    } else {
      // Yes, get it
      result = memory.get(arg);
    }
    
    // We have it (now), return the result
    return result;
  };
}
function fn(arg) {
  console.log("fn called with " + arg);
  return 2 * arg;
}
var m_fn = memoize(fn);
console.log(m_fn(18));
console.log(m_fn(18));
console.log(m_fn(20));
console.log(m_fn(20));

请注意,在这两种情况下,我都详细地编写了代码以允许 cmets 和易于理解。尤其是带有Map 的 ES2015 版本可以相当短很多。

【讨论】:

  • 谢谢! cmets 非常有帮助。我想我现在明白问题所在了。我可以返回一个函数,并且仍然返回我要返回的函数内部的值!那绝对是我的大脑关闭的地方。
  • @LisaEver:你和许多其他人的(包括我的,当我第一次遇到它时)! :-) 这是关于 JavaScript 的非常酷的事情之一。这个概念是我们返回的函数“关闭”了创建它的上下文,并且该上下文包含所有变量,例如调用memoize,因此函数可以访问它们。更多关于我贫血的小博客,虽然文章中的术语已经过时:Closures are not complicated
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-03
  • 2020-08-19
  • 1970-01-01
相关资源
最近更新 更多