immutable

Benchmark created on


Setup

class DeepMapS {
  map = new Map();
  keyMap = new Map();

  constructor(serialize, initialValues) {
    this.serialize = serialize;
    if (initialValues) {
      for (const [key, value] of initialValues) this.set(key, value);
    }
  }

  set(key, value) {
    const keyStr = this.serialize(key);
    this.keyMap.set(keyStr, key);
    this.map.set(keyStr, value);
    return this;
  }

  get(key) {
    return this.map.get(this.serialize(key));
  }

  has(key) {
    return this.keyMap.has(this.serialize(key));
  }

  delete(key) {
    const keyStr = this.serialize(key);
    this.keyMap.delete(keyStr);
    return this.map.delete(keyStr);
  }

  clear() {
    this.map.clear();
    this.keyMap.clear();
  }

  get size() {
    return this.map.size;
  }

  keys() {
    return this.keyMap.values();
  }

  values() {
    return this.map.values();
  }

  forEach(cb, thisArg) {
    this.keyMap.forEach((key, keyStr) => cb.call(thisArg, this.map.get(keyStr), key, this));
  }

  copy() {
    const newMap = new DeepMapS(this.serialize);
    // directly copying the map and keymap (instead of passing it to the constructor), avoids having to recalculate each key hash
    newMap.map = new Map(this.map);
    newMap.keyMap = new Map(this.keyMap);
    return newMap;
  }

  *entries() {
    for (let [keyStr, key] of this.keyMap.entries()) {
      yield [key, this.map.get(keyStr)];
    }
  }

  [Symbol.iterator]() {
    return this.entries();
  }
}

class DeepMapO {
  map = {};
  keyMap = {};

  constructor(serialize, initialValues) {
    this.serialize = serialize;
    if (initialValues?.[Symbol.iterator]) {
      for (const [key, value] of initialValues) this.set(key, value);
    }
  }

  set(key, value) {
    const keyStr = this.serialize(key);
    this.keyMap[keyStr] = key;
    this.map[keyStr] = value;
    return this;
  }

  get(key) {
    return this.map[this.serialize(key)];
  }

  has(key) {
    return !!this.map[this.serialize(key)];
  }

  delete(key) {
    const keyStr = this.serialize(key);
    const hasKey = !!this.map[keyStr];
    delete this.keyMap[hasKey];
    delete this.map[keyStr];
    return hasKey;
  }

  clear() {
    this.map = {};
    this.keyMap = {};
  }

  get size() {
    return Object.keys(this.map).length;
  }

  keys() {
    return Object.values(this.keyMap);
  }

  values() {
    return Object.values(this.map);
  }

  forEach(cb, thisArg) {
        for (let keyStr in this.keyMap) cb.call(thisArg, this.map[keyStr], this.keyMap[keyStr], this);
  }

  copy() {
    const newMap = Object.create(DeepMapO.prototype);
    newMap.serialize = this.serialize
    // directly copying the map and keymap (instead of passing it to the constructor), avoids having to recalculate each key hash
    newMap.map = {...this.map};
    newMap.keyMap = {...this.keyMap};
    return newMap;
  }

  *entries() {
        for (let keyStr in this.keyMap) yield [this.keyMap[keyStr], this.map[keyStr]];
  }

  [Symbol.iterator]() {
    return this.entries();
  }
}

const serialize = k => `${k.chain}__${k.joint}`
const deSerialize = str => {
  const parts = str.split('__');
  return { chain: parts[0], joint: parts[1] };
};
const obj = {}
const mapSerialize = new DeepMapS(serialize);
const mapObject = new DeepMapO(serialize);

for (let chain = 0; chain < 5; chain++) {
  for (let joint = 0; joint < 10; joint++) {
    const key = {
      chain: `cf221f55-957c-43bf-be45-f5be6d2cebe4-${chain}`,
      joint: `dc7d8abf-871c-4980-84ed-c7049a67558d-${joint}`
    };
    const value = `value-${chain}-${joint}`;
    obj[serialize(key)] = value;
    mapObject.set(key, value);
    mapSerialize.set(key, value);
  }
}

const testKey = {
  chain: `cf221f55-957c-43bf-be45-f5be6d2cebe4-${4}`,
  joint: `dc7d8abf-871c-4980-84ed-c7049a67558d-${8}`
};

Test runner

Ready to run.

Testing in
TestOps/sec
obj
return {...obj, [serialize(testKey)]: 'newValue'}
ready
deepmap obj
return mapObject.copy().set(testKey, 'newValue')
ready
deepmap map
return mapSerialize.copy().set(testKey, 'newValue')
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.