备忘函数可以使用ES6映射与标签可使用



preface:尝试通过转换简单的备忘录来学习ES6映射,而不是利用了哈希表。

问题:

可以用new Map()替换起始对象,如果是,如何?并且有任何优势吗?如果没有,为什么?


描述:

这是一个获取功能(add)和起始对象({})的备忘录。在中呼叫时,添加mAdd)参数分布。最后,对哈希索引进行了测试/集合并返回值。

链接到代码

const memo = (fn, hash) => (...a) => {
  return hash[a] === void 0 ?  hash[a] = fn(...a) : `${hash[a]} memo`;
};
const add = (x, y) =>  x + y;
const mAdd = memo(add, {});
console.log(mAdd(2,2));
console.log(mAdd(2,2));

不使用地图:

const memo = (fn, map) => (...a) => {
  return map.get(a) === void 0 ?  map.set(a, fn(...a)) : `${map.get(a)} memo`;
};
const add = (x, y) =>  x + y;
const mAdd = memo(add, new Map());
console.log(mAdd(2,2));
console.log(mAdd(2,2));

主要问题是参数不代表同一对象。他们的内容可以,这就是为什么串行的原因。

使用对象作为哈希,还可以执行一种弦乐:创建了属性2,2。(作为旁注:这不是完整的证明,因为内容是扁平的。参数[1,[2,3]][1,2,3]都将创建属性[1,2,3]

但是,由于Map实际上在某种程度上更聪明,因此对象本身被用作键,对于每个调用,为参数创建了一个新对象。

用相同的参数调用可以工作,但是当然,这会使功能降低有用:

var pars = [2,2];
console.log(mAdd(pars));
console.log(mAdd(pars));

(必须将方法签名更改为const memo = (fn, map) => (a) => {才能工作。还请注意,Map.set返回地图对象本身,而不是要设置的值)。

最简单的实现是将密钥串起。最安全的是处理所有情况的JSON.stringify,但是如果您相对确定内容,则可以执行join之类的事情:

const memo = (fn, map) => (...a) => {
    const key = a.join(',');
  if(map.has(key))
        return `${map.get(key)} memo`;
    let res = fn(...a);
  map.set(key, res );
  return res;
};

创建钥匙可以多种方法。Stringify是可能的,甚至const key = uneval(a);也可以根据长度和内容创建某种类型的哈希整数,但其可靠性取决于可能的内容。例如如果已知值永远不会超过100,并且参数的数量不会太长,则可以使用const key =createKey(a);

来调用const createKey = ([a1,...a]) => a.length ? a1 + 100 * createKey(a) : a1;的助手

当然,对于直接添加的示例,

总是比关键创建和密钥查找更快,但是对于一般目的,创建密钥的方法是定义因素。

我意识到我可能没有在所有这一切中说什么,但是最重要的是传递的参数不代表同一对象。也就是说,我想提出另一个选择:创建一个分支地图。基本映射包含子图(以第一个参数为键)到结果(第二个参数为键)或随后的映射到第二个元素。

编辑上述分支的示例(可以将单个地图用于不同的功能以减少内存足迹):

const memoMap = new Map(); //use a general map for branches for all memoized functions, because result is stored in the child function as the key
const memo = (fn) => (...a) => {
  let key, r = a, map = memoMap;
  while(r.length){
      [key,...r] = r;      
      if(map.has(key))
      	map = map.get(key);
      else
      	map.set(key, map = new Map());
  }
  let res = map.get(fn); //get result for this specific function
  if(res===undefined)
  	map.set(fn, res = fn(...a));
 	else return `${res} memo`; //<-- line for testing purposes
  return res;
};
const add = (x, y) =>  x + y,
  subtr = (x,y) => x - y,
  mAdd = memo(add);
console.log(mAdd(2,2));
console.log(mAdd(2,2));
console.log(memo(subtr)(2,2));
console.log(memo(subtr)(2,2));

问题是地图使用对象的引用以识别为键(如果提供的键是对象而不是原始类型)。在这种情况下, a 变量是一个数组,即使它在每个调用上具有相同的值,但该变量的引用不相同,因此映射将其视为新键。看看底部代码。

const test = new Map();
const a = ['bla'];
test.set(a, 'b');
console.log(test.get(a));
const f = ['bla'];
console.log(test.get(f));

这个问题的一个解决方法是将 a 变量串起。

const memo = (fn, hash) => (...a) => {
  const key = JSON.stringify(a);
  if (hash.has(key)) {
    return `${hash.get(key)} memo`;        
  }
  const val = fn(...a);
  hash.set(key, val);
  return val;
};
const add = (x, y) =>  x + y;
const mAdd = memo(add, new Map());
console.log(mAdd(2,2));
console.log(mAdd(2,2));

注意:您的代码根本不可读取。因此,我不得不对其进行编辑,以使其更容易理解。

最新更新