immutable set (v2)

Revision 2 of this benchmark created on


Preparation HTML

  <script src="https://cdn.jsdelivr.net/npm/dequal@2.0.3/dist/index.min.js"></script>
  <script src="https://unpkg.com/equivalent-key-map/dist/equivalent-key-map.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/immutable@4.3.7/dist/immutable.min.js"></script>

Setup

class DeepMapC {
  map = new Map();

  constructor(equals = dequal.dequal, initialValues) {
    this.equals = equals;
    if (initialValues) this.map = new Map(initialValues);
  }

  findKey(key) {
    for (let k of this.map.keys()) {
      if (this.equals(k, key)) return k;
    }
  }

  set(key, value) {
    this.map.set(this.findKey(key) ?? key, value);
    return this;
  }

  get(key) {
    const existingKey = this.findKey(key);
    if (existingKey) return this.map.get(existingKey);
  }

  has(key) {
    return !!this.findKey(key);
  }

  delete(key) {
    const existingKey = this.findKey(key);
    if (existingKey) return this.map.delete(existingKey);
    else return false;
  }

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

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

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

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

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

  forEach(...args) {
    return this.map.forEach(...args);
  }

  copy() {
    return new DeepMapC(this.serialize, this.map);
  }

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

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) {
      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() {
    for (let keyStr in this.keyMap) yield this.keyMap[keyStr];
  }

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

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

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

  copy() {
    const newMap = new DeepMapO(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;
  }

  [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 map = new Map();
    const obj = {};
    const mapIsEqual = new DeepMapC(dequal.dequal);
    const deepMapMap = new DeepMapS(serialize);
    const deepMapObj = new DeepMapO(serialize);
    const equivalentKeyMap = new EquivalentKeyMap();
    let immutableMap = new Immutable.Map();

    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}`;
        map.set(serialize(key), value);
        obj[serialize(key)] = value;
        equivalentKeyMap.set(key, value);
        immutableMap = immutableMap.set(Immutable.fromJS(key), value);
        mapIsEqual.set(key, value);
        deepMapMap.set(key, value);
        deepMapObj.set(key, value);
      }
    }

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

Test runner

Ready to run.

Testing in
TestOps/sec
native map
return new Map(map).set(serialize(testKey), newValue)
ready
native object
return {...obj, [serialize(testKey)]: newValue}
ready
equivalentKeyMap
return new EquivalentKeyMap(map).set(testKey, newValue)
ready
immutableMap
return immutableMap.set(Immutable.fromJS(testKey), newValue)
ready
deepMap deep isEqual
return mapIsEqual.copy().set(testKey, newValue)
ready
deepMap map
return deepMapMap.copy().set(testKey, newValue)
ready
deepMap obj
return deepMapObj.copy().set(testKey, newValue)
ready

Revisions

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