javascript - 如何制作懒惰集?

标签 javascript ecmascript-6 iterator hashset iterable

new Set(getObjects())

getObjects 返回一个可迭代对象。每次迭代都会创建一个新对象。但是 Set 立即获取并添加所有新对象。

如何使得只有当需要新对象时,才获取并添加新对象?

我尝试了 Proxy,但在使用 receiver 作为 this 调用函数时,没有处理程序触发。

const getEntitySet = (nativeEntityCollection) => {
  const cache = new Set;

  let src = {
    [Symbol.iterator]: ((itr) => {
      return () => itr;
    })(nativeEntityCollection[Symbol.iterator]()),
    ref: nativeEntityCollection
  };

  let addAll = () => {
    for (const entity of src) {
      cache.add(entity)
    }
    done()
  };

  let generate = function*() {
    yield* cache;
    for (const entity of src) {
      cache.add(entity);
      yield entity;
    }
    done()
  };

  const done = () => {
    src = null;
    addAll = null;
    generate = Reflect.get(cache, Symbol.iterator).bind(cache)
  };

  const generateRef = () => generate();
  return new Proxy(cache, {
    get(set, prop) {
      if (prop === Symbol.iterator) {
        return generateRef;
      }
      addAll?.();
      return Reflect.get(cache, prop);
    }
  });
};

最佳答案

当在代理对象上使用 Symbol.iterator 以外的其他方法时,可以重现您提到的接收器问题,例如 has

这可以通过使用 bind 来修复(就像您在其他地方已经做的那样):

return Reflect.get(cache, prop).bind(cache);

这将解决该问题。

其他备注

对方法的访问将触发addAll,这很遗憾,因为不确定此访问是否真的需要发生。例如,如果我们只是做了 const f = myproxy.has,那么实际上没有必要贪婪地消耗可迭代对象。

使用代理模式,您必须返回一个替换函数才能进行此类方法访问,并且只能在该返回函数中调用 addAll。我建议使用继承模式而不是代理模式。这似乎更适合实现所需的行为。

对于 has 方法,我们甚至可以考虑部分消耗可迭代对象,直到找到目标元素为止,因为任何进一步的消耗该 iterable 永远不会改变此方法调用的结果。

实现

这是您可以考虑的另一种“子类化”实现:

class LazySet extends Set {
    #iterator
    
    constructor(iterable) {
        super();
        this.#iterator = iterable[Symbol.iterator]();
        this.#iterator.return = () => ({}); // Disable undesired return behaviour
    }
    * values() {
        yield* super.values();
        if (!this.#iterator) return;
        let size = super.size;
        for (const value of this.#iterator) {
            super.add(value); // lazy
            if (size === super.size) continue; // duplicate
            yield value;
            size++;
        }
        this.#iterator = null; // release reference
    }
    * entries() {
        for (const value of this.values()) yield [value, value];
    }
    * keys() {
        yield* this.values();
    }
    * [Symbol.iterator]() {
        yield* this.values();
    }
    forEach(cb, thisArg) {
        for (const value of this.values()) {
            cb.call(thisArg, value, this);
        }
    }
    has(needle) {
        if (super.has(needle)) return true;
        if (!this.#iterator) return false;
        for (const value of this.#iterator) {
            super.add(value); // lazy
            if (value === needle) return true;
        }
        this.#iterator = null;
        return false;
    }
    delete(needle) {
        for (const _ of this.values()) {}; // consume iterator
        return super.delete(needle);
    }
    get size() {
        for (const _ of this.values()) {}; // consume iterator
        return super.size;
    } 
}

// Demo
function* generator() {
    for (let i = 0; i < 10; i++) {
        console.log(`  (about to yield ${i})`);
        yield i;
    }
}

let set = new LazySet(generator());
console.log("Does set have 3?:");
console.log(set.has(3));
console.log("-----------------");
console.log("first 5 values in set:");
let i = 0;
for (const value of set) {
    console.log(value);
    if (++i === 5) break;
}
console.log("-----------------");
console.log("All values in set:");
console.log(...set);
console.log("size:", set.size);

关于javascript - 如何制作懒惰集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74426639/

相关文章:

javascript - 如何保持 $.getJSON 响应的顺序?

javascript - map 标记不聚类

javascript - for-of 循​​环的性能成本

c++ - 如何一一删除STL deque的所有元素?

loops - 为什么我不能在两个不同的映射函数中可变地借用变量?

javascript - React 样式可重用组件不是样式组件

javascript - 如何在 JavaScript ES6 中使用数组解构并分配给对象属性

javascript - 将缺失值添加到分组数据中

c++ - 带有 vector 指针的迭代器

javascript - 添加到 WordPress header 时脚本损坏